erlangerlang-driver

Erlang driver erl_errno issue


I am playing with erl_driver. Start callback of my driver is below:

ErlDrvData drv_start(ErlDrvPort port, char* command) { char* file_name = command + sizeof(drv_name); GenTtyData* port_data = (GenTtyData*)driver_alloc(sizeof(GenTtyData)); erl_errno = gt_open(file_name, &port_data->file); if (erl_errno != 0) { // Assertion there is just to show you my intention assert(erl_errno == ENOENT); driver_free(port_data); return ERL_DRV_ERROR_ERRNO; } return port_data; }

Documentation says

ErlDrvData (start)(ErlDrvPort port, char command)

This is called when the driver is instantiated, when open_port/2 is called. The driver should return a number >= 0 or a pointer, or if the driver can't be started, one of three error codes should be returned:

ERL_DRV_ERROR_GENERAL - general error, no error code

ERL_DRV_ERROR_ERRNO - error with error code in erl_errno

ERL_DRV_ERROR_BADARG - error, badarg

If an error code is returned, the port isn't started.

So I expect that erlang:open_port/2 will send exit signal containing error description (eacces for example, or enotty). However, in test I have

{'EXIT', {unknown, [{erlang,open_port,[{spawn,"gen_tty_drv non_existent.tty"},[]],[]}, {gen_tty,open,1, [{file, "/home/vkovalev/workspace/gen_tty_drv/_build/test/lib/gen_tty/src/gen_tty.erl"}, {line,48}]}, {gen_tty_drv_SUITE,open_non_existent_file,1, [{file, "/home/vkovalev/workspace/gen_tty_drv/test/gen_tty_drv_SUITE.erl"}, {line,29}]}, {test_server,ts_tc,3,[{file,"test_server.erl"},{line,1450}]}, {test_server,run_test_case_eval1,6, [{file,"test_server.erl"},{line,1026}]}, {test_server,run_test_case_eval,9, [{file,"test_server.erl"},{line,968}]}]}}

It seems erlang doesn't see or can't decode error reason and sends unknown. So what's wrong with my start callback?


Solution

  • This seems like a bug either in the documentation or the code. If the code that invokes the driver start function finds that the function returns ERL_DRV_ERROR_ERRNO, it takes the error number from errno, not erl_errno (this is true as of otp master commit dc35ae6c, anyway, which is roughly Erlang version 18.0-rc2; you might find it's different in later versions).

    To prove this, I wrote a driver start function that sets errno explicitly:

    static ErlDrvData
    my_driver_start(ErlDrvPort port, char *command)
    {
        errno = ETXTBSY;
        return ERL_DRV_ERROR_ERRNO;
    }
    

    and with this approach, I got the result you're seeking:

    1> my_driver:start().
    ** exception error: etxtbsy
         in function  open_port/2
            called as open_port({spawn,my_driver},[binary])
         in call from my_driver:start/0 (my_driver.erl, line 10)
    

    Setting erl_errno in the same manner, though, did not produce this result. If I explicitly set errno to a garbage value and then set erl_errno to a valid error number, like this:

    static ErlDrvData
    my_driver_start(ErlDrvPort port, char *command)
    {
        errno = 3245690989;
        erl_errno = ETXTBSY;
        return ERL_DRV_ERROR_ERRNO;
    }
    

    then I get the same unknown result as you:

    1> my_driver:start().
    ** exception error: unknown
         in function  open_port/2
            called as open_port({spawn,my_driver},[binary])
         in call from my_driver:start/0 (my_driver.erl, line 10)
    

    Update: I contacted a member of the OTP team about this and they replied by email that this is a documentation bug, which means using errno rather than erl_errno is the right solution. They said this part of the driver code has always used errno.

    Update 2: This issue has been fixed in the driver_entry documentation as of Erlang 18.0.