Skip to content

error.Unexpected on Windows when attempting to read/write disconnected USB Virtual Com Port #23648

Open
@psbob

Description

@psbob

Zig Version

0.15.0-dev.383+927f233ff

Steps to Reproduce and Observed Behavior

When using a USB serial adapter on Windows, reads/writes cause error.Unexpected if the USB adapter has been disconnected after opening the port. Windows is reporting an 'Access is Denied' error (5). For my use case it will be OK to catch the error.Unexpected to detect if the USB adapter has been disconnected, but it seemed sensible to report the issue.

The following test reproduces the issue if a USB serial port is attached with the name 'COM5' and is disconnected when prompted:

const std = @import("std");
const port = "COM5";

test "USB_serial_disconnected" {

    if (@import("builtin").os.tag != .windows) return error.SkipZigTest;

    var serial_port: std.fs.File = try std.fs.cwd().openFile("\\\\.\\" ++ port, .{ .mode = .read_write });
    defer serial_port.close();

    var readbyte: u8 = 0;

    std.log.warn("Disconnect USB adapter now\n", .{});

    // blocks until receives data or USB disconnected
    readbyte = serial_port.reader().readByte() catch |err| switch (err) {
        // error.EndOfStream => 0, // expected if no data and serial timeout set, not relevant in this example
        error.Unexpected => 0,
        error.AccessDenied => 0,
        else => unreachable,
    };

    serial_port.writer().writeByte(readbyte) catch |err| switch (err) {
        error.Unexpected => {},
        error.AccessDenied => {},
        else => unreachable,
    };
}

The above test produces this output:

PS C:\testdir> .\zig.exe test usb.zig
[default] (warn): Disconnect USB adapter now

error.Unexpected: GetLastError(5): Access is denied.

C:\testdir\lib\std\os\windows.zig:637:53: 0x7ff632375376 in ReadFile (test.exe.obj)
                else => |err| return unexpectedError(err),
                                                    ^
C:\testdir\lib\std\fs\File.zig:1181:32: 0x7ff6323721d7 in read (test.exe.obj)
        return windows.ReadFile(self.handle, buffer, null);
                               ^
C:\testdir\lib\std\io.zig:287:26: 0x7ff63237170a in typeErasedReadFn (test.exe.obj)
            return readFn(ptr.*, buffer);
                         ^
C:\testdir\lib\std\io\Reader.zig:10:23: 0x7ff6323722bb in read (test.exe.obj)
    return self.readFn(self.context, buffer);
                      ^
C:\testdir\lib\std\io\Reader.zig:234:35: 0x7ff632371822 in readByte (test.exe.obj)
    const amt_read = try self.read(result[0..]);
                                  ^
C:\testdir\usb.zig:16:45: 0x7ff63237113b in test.USB_serial_disconnected (test.exe.obj)
    readbyte = serial_port.reader().readByte() catch |err| switch (err) {
                                            ^
C:\testdir\lib\compiler\test_runner.zig:214:25: 0x7ff6323f79cc in mainTerminal (test.exe.obj)
        if (test_fn.func()) |_| {
                        ^
C:\testdir\lib\compiler\test_runner.zig:62:28: 0x7ff6323f0fb8 in main (test.exe.obj)
        return mainTerminal();
                           ^
C:\testdir\lib\std\start.zig:490:53: 0x7ff6323f0a82 in WinStartup (test.exe.obj)
    std.os.windows.ntdll.RtlExitUserProcess(callMain());
                                                    ^
???:?:?: 0x7ffa205c7373 in ??? (KERNEL32.DLL)
???:?:?: 0x7ffa20ddcc90 in ??? (ntdll.dll)
error.Unexpected: GetLastError(5): Access is denied.

C:\testdir\lib\std\os\windows.zig:690:49: 0x7ff6323768ab in WriteFile (test.exe.obj)
            else => |err| return unexpectedError(err),
                                                ^
C:\testdir\lib\std\fs\File.zig:1325:33: 0x7ff632372b27 in write (test.exe.obj)
        return windows.WriteFile(self.handle, bytes, null);
                                ^
C:\testdir\lib\std\io.zig:348:27: 0x7ff6323719ba in typeErasedWriteFn (test.exe.obj)
            return writeFn(ptr.*, bytes);
                          ^
C:\testdir\lib\std\io\Writer.zig:13:24: 0x7ff63237693b in write (test.exe.obj)
    return self.writeFn(self.context, bytes);
                       ^
C:\testdir\lib\std\io\Writer.zig:19:32: 0x7ff632372c6e in writeAll (test.exe.obj)
        index += try self.write(bytes[index..]);
                               ^
C:\testdir\lib\std\io\Writer.zig:29:25: 0x7ff632371a57 in writeByte (test.exe.obj)
    return self.writeAll(&array);
                        ^
C:\testdir\usb.zig:22:35: 0x7ff6323712f6 in test.USB_serial_disconnected (test.exe.obj)
    serial_port.writer().writeByte(readbyte) catch |err| switch (err) {
                                  ^
C:\testdir\lib\compiler\test_runner.zig:214:25: 0x7ff6323f79cc in mainTerminal (test.exe.obj)
        if (test_fn.func()) |_| {
                        ^
C:\testdir\lib\compiler\test_runner.zig:62:28: 0x7ff6323f0fb8 in main (test.exe.obj)
        return mainTerminal();
                           ^
C:\testdir\lib\std\start.zig:490:53: 0x7ff6323f0a82 in WinStartup (test.exe.obj)
    std.os.windows.ntdll.RtlExitUserProcess(callMain());
                                                    ^
???:?:?: 0x7ffa205c7373 in ??? (KERNEL32.DLL)
???:?:?: 0x7ffa20ddcc90 in ??? (ntdll.dll)
All 1 tests passed.

Currently the only windows machine I can use belongs to my employer and has an AV which cannot be disabled, so I cannot rule out the AV causing the Access Denied error.

Expected Behavior

error.AccessDenied is returned from a read or write attempt when the USB adapter has been disconnected.

The above test passes when the switch statements in /lib/std/os/windows.zig (ReadFile and WriteFile) are modified to include an '.ACCESS_DENIED' option, and the ReadFileError/WriteFileError error sets in the same file are updated to include AccessDenied.

If the maintainers are interested in including this I would be happy to submit a PR updating the ReadFile and WriteFile functions and associated error sets.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions