authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-06-02 15:31:14-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-06-02 15:31:14-04:00
log14d235dd6e79dba8d34e05f1912a1bcf9a3ef68c
tree9122d4a2eab8b9deef81e089cecee0e7699a08fa
parentbae0c9b554105092297330c8beec977fe3468da6
parent7fd937fef4547a98d7c33ea67eca76e6336f9152

Merge branch 'lun-4-resolve-ip6'

closes #4864

6 files changed, 276 insertions(+), 15 deletions(-)

lib/std/c.zig+1
...@@ -132,6 +132,7 @@ pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;...@@ -132,6 +132,7 @@ pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
132pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;132pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
133pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;133pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
134pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int;134pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int;
135pub extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int;
135pub extern "c" fn uname(buf: *utsname) c_int;136pub extern "c" fn uname(buf: *utsname) c_int;
136137
137pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;138pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
lib/std/net.zig+173-1
...@@ -21,6 +21,9 @@ pub const Address = extern union {...@@ -21,6 +21,9 @@ pub const Address = extern union {
21 // TODO this crashed the compiler. https://github.com/ziglang/zig/issues/351221 // TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512
22 //pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 0);22 //pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 0);
2323
24 /// Parse the given IP address string into an Address value.
25 /// It is recommended to use `resolveIp` instead, to handle
26 /// IPv6 link-local unix addresses.
24 pub fn parseIp(name: []const u8, port: u16) !Address {27 pub fn parseIp(name: []const u8, port: u16) !Address {
25 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {28 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
26 error.Overflow,29 error.Overflow,
...@@ -42,6 +45,28 @@ pub const Address = extern union {...@@ -42,6 +45,28 @@ pub const Address = extern union {
42 return error.InvalidIPAddressFormat;45 return error.InvalidIPAddressFormat;
43 }46 }
4447
48 pub fn resolveIp(name: []const u8, port: u16) !Address {
49 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
50 error.Overflow,
51 error.InvalidEnd,
52 error.InvalidCharacter,
53 error.Incomplete,
54 => {},
55 }
56
57 if (resolveIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
58 error.Overflow,
59 error.InvalidEnd,
60 error.InvalidCharacter,
61 error.Incomplete,
62 error.InvalidIpv4Mapping,
63 => {},
64 else => return err,
65 }
66
67 return error.InvalidIPAddressFormat;
68 }
69
45 pub fn parseExpectingFamily(name: []const u8, family: os.sa_family_t, port: u16) !Address {70 pub fn parseExpectingFamily(name: []const u8, family: os.sa_family_t, port: u16) !Address {
46 switch (family) {71 switch (family) {
47 os.AF_INET => return parseIp4(name, port),72 os.AF_INET => return parseIp4(name, port),
...@@ -51,6 +76,9 @@ pub const Address = extern union {...@@ -51,6 +76,9 @@ pub const Address = extern union {
51 }76 }
52 }77 }
5378
79 /// Parse a given IPv6 address string into an Address.
80 /// Assumes the Scope ID of the address is fully numeric.
81 /// For non-numeric addresses, see `resolveIp6`.
54 pub fn parseIp6(buf: []const u8, port: u16) !Address {82 pub fn parseIp6(buf: []const u8, port: u16) !Address {
55 var result = Address{83 var result = Address{
56 .in6 = os.sockaddr_in6{84 .in6 = os.sockaddr_in6{
...@@ -157,6 +185,136 @@ pub const Address = extern union {...@@ -157,6 +185,136 @@ pub const Address = extern union {
157 }185 }
158 }186 }
159187
188 pub fn resolveIp6(buf: []const u8, port: u16) !Address {
189 // TODO: Unify the implementations of resolveIp6 and parseIp6.
190 var result = Address{
191 .in6 = os.sockaddr_in6{
192 .scope_id = 0,
193 .port = mem.nativeToBig(u16, port),
194 .flowinfo = 0,
195 .addr = undefined,
196 },
197 };
198 var ip_slice = result.in6.addr[0..];
199
200 var tail: [16]u8 = undefined;
201
202 var x: u16 = 0;
203 var saw_any_digits = false;
204 var index: u8 = 0;
205 var abbrv = false;
206
207 var scope_id = false;
208 var scope_id_value: [os.IFNAMESIZE - 1]u8 = undefined;
209 var scope_id_index: usize = 0;
210
211 for (buf) |c, i| {
212 if (scope_id) {
213 // Handling of percent-encoding should be for an URI library.
214 if ((c >= '0' and c <= '9') or
215 (c >= 'A' and c <= 'Z') or
216 (c >= 'a' and c <= 'z') or
217 (c == '-') or (c == '.') or (c == '_') or (c == '~'))
218 {
219 if (scope_id_index >= scope_id_value.len) {
220 return error.Overflow;
221 }
222
223 scope_id_value[scope_id_index] = c;
224 scope_id_index += 1;
225 } else {
226 return error.InvalidCharacter;
227 }
228 } else if (c == ':') {
229 if (!saw_any_digits) {
230 if (abbrv) return error.InvalidCharacter; // ':::'
231 if (i != 0) abbrv = true;
232 mem.set(u8, ip_slice[index..], 0);
233 ip_slice = tail[0..];
234 index = 0;
235 continue;
236 }
237 if (index == 14) {
238 return error.InvalidEnd;
239 }
240 ip_slice[index] = @truncate(u8, x >> 8);
241 index += 1;
242 ip_slice[index] = @truncate(u8, x);
243 index += 1;
244
245 x = 0;
246 saw_any_digits = false;
247 } else if (c == '%') {
248 if (!saw_any_digits) {
249 return error.InvalidCharacter;
250 }
251 scope_id = true;
252 saw_any_digits = false;
253 } else if (c == '.') {
254 if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
255 // must start with '::ffff:'
256 return error.InvalidIpv4Mapping;
257 }
258 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
259 const addr = (parseIp4(buf[start_index..], 0) catch {
260 return error.InvalidIpv4Mapping;
261 }).in.addr;
262 ip_slice = result.in6.addr[0..];
263 ip_slice[10] = 0xff;
264 ip_slice[11] = 0xff;
265
266 const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
267
268 ip_slice[12] = ptr[0];
269 ip_slice[13] = ptr[1];
270 ip_slice[14] = ptr[2];
271 ip_slice[15] = ptr[3];
272 return result;
273 } else {
274 const digit = try std.fmt.charToDigit(c, 16);
275 if (@mulWithOverflow(u16, x, 16, &x)) {
276 return error.Overflow;
277 }
278 if (@addWithOverflow(u16, x, digit, &x)) {
279 return error.Overflow;
280 }
281 saw_any_digits = true;
282 }
283 }
284
285 if (!saw_any_digits and !abbrv) {
286 return error.Incomplete;
287 }
288
289 if (scope_id and scope_id_index == 0) {
290 return error.Incomplete;
291 }
292
293 var resolved_scope_id: u32 = 0;
294 if (scope_id_index > 0) {
295 const scope_id_str = scope_id_value[0..scope_id_index];
296 resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: {
297 if (err != error.InvalidCharacter) return err;
298 break :blk try if_nametoindex(scope_id_str);
299 };
300 }
301
302 result.in6.scope_id = resolved_scope_id;
303
304 if (index == 14) {
305 ip_slice[14] = @truncate(u8, x >> 8);
306 ip_slice[15] = @truncate(u8, x);
307 return result;
308 } else {
309 ip_slice[index] = @truncate(u8, x >> 8);
310 index += 1;
311 ip_slice[index] = @truncate(u8, x);
312 index += 1;
313 mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
314 return result;
315 }
316 }
317
160 pub fn parseIp4(buf: []const u8, port: u16) !Address {318 pub fn parseIp4(buf: []const u8, port: u16) !Address {
161 var result = Address{319 var result = Address{
162 .in = os.sockaddr_in{320 .in = os.sockaddr_in{
...@@ -380,6 +538,20 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {...@@ -380,6 +538,20 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
380 };538 };
381}539}
382540
541fn if_nametoindex(name: []const u8) !u32 {
542 var ifr: os.ifreq = undefined;
543 var sockfd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM | os.SOCK_CLOEXEC, 0);
544 defer os.close(sockfd);
545
546 std.mem.copy(u8, &ifr.ifrn.name, name);
547 ifr.ifrn.name[name.len] = 0;
548
549 // TODO investigate if this needs to be integrated with evented I/O.
550 try os.ioctl_SIOCGIFINDEX(sockfd, &ifr);
551
552 return @bitCast(u32, ifr.ifru.ivalue);
553}
554
383pub const AddressList = struct {555pub const AddressList = struct {
384 arena: std.heap.ArenaAllocator,556 arena: std.heap.ArenaAllocator,
385 addrs: []Address,557 addrs: []Address,
...@@ -1071,7 +1243,7 @@ fn linuxLookupNameFromNumericUnspec(...@@ -1071,7 +1243,7 @@ fn linuxLookupNameFromNumericUnspec(
1071 name: []const u8,1243 name: []const u8,
1072 port: u16,1244 port: u16,
1073) !void {1245) !void {
1074 const addr = try Address.parseIp(name, port);1246 const addr = try Address.resolveIp(name, port);
1075 (try addrs.addOne()).* = LookupAddr{ .addr = addr };1247 (try addrs.addOne()).* = LookupAddr{ .addr = addr };
1076}1248}
10771249
lib/std/net/test.zig+22
...@@ -34,6 +34,12 @@ test "parse and render IPv6 addresses" {...@@ -34,6 +34,12 @@ test "parse and render IPv6 addresses" {
34 var addr = net.Address.parseIp6(ip, 0) catch unreachable;34 var addr = net.Address.parseIp6(ip, 0) catch unreachable;
35 var newIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable;35 var newIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable;
36 std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));36 std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
37
38 if (std.builtin.os.tag == .linux) {
39 var addr_via_resolve = net.Address.resolveIp6(ip, 0) catch unreachable;
40 var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr_via_resolve}) catch unreachable;
41 std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
42 }
37 }43 }
3844
39 testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0));45 testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0));
...@@ -42,6 +48,22 @@ test "parse and render IPv6 addresses" {...@@ -42,6 +48,22 @@ test "parse and render IPv6 addresses" {
42 testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));48 testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));
43 testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0));49 testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0));
44 testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0));50 testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0));
51 // TODO Make this test pass on other operating systems.
52 if (std.builtin.os.tag == .linux) {
53 testing.expectError(error.Incomplete, net.Address.resolveIp6("ff01::fb%", 0));
54 testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%wlp3s0s0s0s0s0s0s0s0", 0));
55 testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%12345678901234", 0));
56 }
57}
58
59test "invalid but parseable IPv6 scope ids" {
60 if (std.builtin.os.tag != .linux) {
61 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
62 // TODO Make this test pass on other operating systems.
63 return error.SkipZigTest;
64 }
65
66 testing.expectError(error.InterfaceNotFound, net.Address.resolveIp6("ff01::fb%123s45678901234", 0));
45}67}
4668
47test "parse and render IPv4 addresses" {69test "parse and render IPv4 addresses" {
lib/std/os.zig+44-14
...@@ -405,7 +405,6 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -405,7 +405,6 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
405 else => |err| return unexpectedErrno(err),405 else => |err| return unexpectedErrno(err),
406 }406 }
407 }407 }
408
409 const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);408 const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
410 while (true) {409 while (true) {
411 // TODO handle the case when iov_len is too large and get rid of this @intCast410 // TODO handle the case when iov_len is too large and get rid of this @intCast
...@@ -2391,8 +2390,15 @@ pub fn isatty(handle: fd_t) bool {...@@ -2391,8 +2390,15 @@ pub fn isatty(handle: fd_t) bool {
2391 return true;2390 return true;
2392 }2391 }
2393 if (builtin.os.tag == .linux) {2392 if (builtin.os.tag == .linux) {
2394 var wsz: linux.winsize = undefined;2393 while (true) {
2395 return linux.syscall3(.ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0;2394 var wsz: linux.winsize = undefined;
2395 const fd = @bitCast(usize, @as(isize, handle));
2396 switch (linux.syscall3(.ioctl, fd, linux.TIOCGWINSZ, @ptrToInt(&wsz))) {
2397 0 => return true,
2398 EINTR => continue,
2399 else => return false,
2400 }
2401 }
2396 }2402 }
2397 unreachable;2403 unreachable;
2398}2404}
...@@ -2451,9 +2457,8 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t...@@ -2451,9 +2457,8 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
2451 if (builtin.os.tag == .windows) {2457 if (builtin.os.tag == .windows) {
2452 // NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into windows-analagous operations2458 // NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into windows-analagous operations
2453 const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC);2459 const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC);
2454 const flags : u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;2460 const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;
2455 const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type),2461 const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type), @intCast(c_int, protocol), null, 0, flags);
2456 @intCast(c_int, protocol), null, 0, flags);
2457 if (rc == windows.ws2_32.INVALID_SOCKET) switch (windows.ws2_32.WSAGetLastError()) {2462 if (rc == windows.ws2_32.INVALID_SOCKET) switch (windows.ws2_32.WSAGetLastError()) {
2458 .WSAEMFILE => return error.ProcessFdQuotaExceeded,2463 .WSAEMFILE => return error.ProcessFdQuotaExceeded,
2459 .WSAENOBUFS => return error.SystemResources,2464 .WSAENOBUFS => return error.SystemResources,
...@@ -2463,7 +2468,7 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t...@@ -2463,7 +2468,7 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
2463 };2468 };
2464 errdefer windows.closesocket(rc) catch unreachable;2469 errdefer windows.closesocket(rc) catch unreachable;
2465 if ((socket_type & SOCK_NONBLOCK) != 0) {2470 if ((socket_type & SOCK_NONBLOCK) != 0) {
2466 var mode : c_ulong = 1; // nonblocking2471 var mode: c_ulong = 1; // nonblocking
2467 if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) {2472 if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) {
2468 switch (windows.ws2_32.WSAGetLastError()) {2473 switch (windows.ws2_32.WSAGetLastError()) {
2469 // have not identified any error codes that should be handled yet2474 // have not identified any error codes that should be handled yet
...@@ -2858,7 +2863,7 @@ pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) Con...@@ -2858,7 +2863,7 @@ pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) Con
2858 .WSAECONNREFUSED => return error.ConnectionRefused,2863 .WSAECONNREFUSED => return error.ConnectionRefused,
2859 .WSAETIMEDOUT => return error.ConnectionTimedOut,2864 .WSAETIMEDOUT => return error.ConnectionTimedOut,
2860 .WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well?2865 .WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well?
2861 ,.WSAENETUNREACH => return error.NetworkUnreachable,2866 , .WSAENETUNREACH => return error.NetworkUnreachable,
2862 .WSAEFAULT => unreachable,2867 .WSAEFAULT => unreachable,
2863 .WSAEINVAL => unreachable,2868 .WSAEINVAL => unreachable,
2864 .WSAEISCONN => unreachable,2869 .WSAEISCONN => unreachable,
...@@ -4882,12 +4887,15 @@ pub fn getrusage(who: i32) rusage {...@@ -4882,12 +4887,15 @@ pub fn getrusage(who: i32) rusage {
4882pub const TermiosGetError = error{NotATerminal} || UnexpectedError;4887pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
48834888
4884pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {4889pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
4885 var term: termios = undefined;4890 while (true) {
4886 switch (errno(system.tcgetattr(handle, &term))) {4891 var term: termios = undefined;
4887 0 => return term,4892 switch (errno(system.tcgetattr(handle, &term))) {
4888 EBADF => unreachable,4893 0 => return term,
4889 ENOTTY => return error.NotATerminal,4894 EINTR => continue,
4890 else => |err| return unexpectedErrno(err),4895 EBADF => unreachable,
4896 ENOTTY => return error.NotATerminal,
4897 else => |err| return unexpectedErrno(err),
4898 }
4891 }4899 }
4892}4900}
48934901
...@@ -4906,3 +4914,25 @@ pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) Termio...@@ -4906,3 +4914,25 @@ pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) Termio
4906 }4914 }
4907 }4915 }
4908}4916}
4917
4918const IoCtl_SIOCGIFINDEX_Error = error{
4919 FileSystem,
4920 InterfaceNotFound,
4921} || UnexpectedError;
4922
4923pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
4924 while (true) {
4925 switch (errno(system.ioctl(fd, SIOCGIFINDEX, @ptrToInt(ifr)))) {
4926 0 => return,
4927 EINVAL => unreachable, // Bad parameters.
4928 ENOTTY => unreachable,
4929 ENXIO => unreachable,
4930 EBADF => unreachable, // Always a race condition.
4931 EFAULT => unreachable, // Bad pointer parameter.
4932 EINTR => continue,
4933 EIO => return error.FileSystem,
4934 ENODEV => return error.InterfaceNotFound,
4935 else => |err| return unexpectedErrno(err),
4936 }
4937 }
4938}
lib/std/os/bits/linux.zig+32
...@@ -1705,3 +1705,35 @@ pub const termios = extern struct {...@@ -1705,3 +1705,35 @@ pub const termios = extern struct {
1705 ispeed: speed_t,1705 ispeed: speed_t,
1706 ospeed: speed_t,1706 ospeed: speed_t,
1707};1707};
1708
1709pub const SIOCGIFINDEX = 0x8933;
1710pub const IFNAMESIZE = 16;
1711
1712pub const ifmap = extern struct {
1713 mem_start: u32,
1714 mem_end: u32,
1715 base_addr: u16,
1716 irq: u8,
1717 dma: u8,
1718 port: u8,
1719};
1720
1721pub const ifreq = extern struct {
1722 ifrn: extern union {
1723 name: [IFNAMESIZE]u8,
1724 },
1725 ifru: extern union {
1726 addr: sockaddr,
1727 dstaddr: sockaddr,
1728 broadaddr: sockaddr,
1729 netmask: sockaddr,
1730 hwaddr: sockaddr,
1731 flags: i16,
1732 ivalue: i32,
1733 mtu: i32,
1734 map: ifmap,
1735 slave: [IFNAMESIZE - 1:0]u8,
1736 newname: [IFNAMESIZE - 1:0]u8,
1737 data: ?[*]u8,
1738 },
1739};
lib/std/os/linux.zig+4
...@@ -1193,6 +1193,10 @@ pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usi...@@ -1193,6 +1193,10 @@ pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usi
1193 return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), TCSETS + @enumToInt(optional_action), @ptrToInt(termios_p));1193 return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), TCSETS + @enumToInt(optional_action), @ptrToInt(termios_p));
1194}1194}
11951195
1196pub fn ioctl(fd: fd_t, request: u32, arg: usize) usize {
1197 return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), request, arg);
1198}
1199
1196test "" {1200test "" {
1197 if (builtin.os.tag == .linux) {1201 if (builtin.os.tag == .linux) {
1198 _ = @import("linux/test.zig");1202 _ = @import("linux/test.zig");