clinuxraspberry-pidaemond2xx

Why does my D2XX application not work when forked?


I'm writing a simple application in C, running on a Raspberry Pi, that makes use of the D2XX drivers to communicate with a serial port device. I've followed a number of online tutorials and reference guides to get it working, and have taken steps such as setting up custom udev rules to ensure the drivers can load properly, I followed FTDI's build instructions to install the shared library, I use the -l argument of gcc to link in the library when compiling, and I run my C program with sudo to ensure the drivers have proper access. And that has been a success! The program works as intended.

Now I am trying to convert my simple program into a daemon process that can be controlled with an init.d script (a la service start), and have run into trouble.

For simplicity's sake, here is a watered down version of my C program which does work:

myprog.c:

#include <stdlib.h>
#include "ftd2xx.h"

int main(int argc, char *argv[])
{
    DWORD i, iNumDevs = 0;
    char *serialNumber = malloc(64);
    FT_STATUS ftStatus = FT_CreateDeviceInfoList(&iNumDevs);
    for (i = 0; i < iNumDevs; i++) {
        ftStatus = FT_ListDevices((PVOID)i, serialNumber, FT_LIST_BY_INDEX|FT_OPEN_BY_SERIAL_NUMBER);
        if (FT_OK == ftStatus) {
            break;
        }
    }

    // more code here...

    return EXIT_SUCCESS;
}

I compile that with gcc -lftd2xx -o myprog myprog.c and then run it with sudo ./myprog, and take my word for it that it does everything that it's supposed to do. But now that I am trying to re-work this same code into a daemon, I've been following some other online tutorials, and the code above has been transformed into something that looks more like this. Currently, this does not work:

mydaemon.c:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "ftd2xx.h"

int main(int argc, char *argv[])
{
    pid_t pid, sid;
    pid = fork();
    if (pid < 0) {
        return EXIT_FAILURE;
    }

    if (pid > 0) {
        return EXIT_SUCCESS;
    }

    umask(0);
    openlog("mydaemon", LOG_PID|LOG_CONS, LOG_USER);

    sid = setsid();
    if (sid < 0) {
        syslog(LOG_ERR, "Failed to set session ID on child process");
        return EXIT_FAILURE;
    }

    if ((chdir("/")) < 0) {
        syslog(LOG_ERR, "Failed to change working directory");
        return EXIT_FAILURE;
    }

    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    while (1) {

        DWORD i, iNumDevs = 0;
        char *serialNumber = malloc(64);

        syslog(LOG_INFO, "I get to this line");
        FT_STATUS ftStatus = FT_CreateDeviceInfoList(&iNumDevs);
        syslog(LOG_INFO, "I do not get to this line :( ");

        // more code here...

        sleep(10);
    }

    return EXIT_SUCCESS;
}

I compile that program in the exact same way: gcc -lftd2xx -o mydaemon mydaemon.c; I run it the same way: sudo ./mydaemon, but unfortunately it does not work. In a separate console window I'm tailing the /var/log/messages file, and I can clearly see it reach my first log message (i.e. "I can get to this line"), but immediately after that it is dead in the water. I never see the second log message, and indeed, at that point the program becomes totally unresponsive. I have to find its process ID and kill it.

In other words, as soon as it tries to make a call to the D2XX drivers in the forked process, it fails. What am I doing wrong? I've already demonstrated with the first example that the code does work, so what is it about running as a daemon that causes it to completely break down? As far as I can tell it doesn't even get a chance to execute the D2XX method in question; it's as if it simply can't find the method in the first place, while running in the forked process.


Solution

  • Probably because it uses libusb... and they appear to do it badly.

    See my question here: libusb-1.0 hotplug events stop working in parent after fork(), when child calls libusb_exit()

    And the discussion here: https://github.com/libusb/libusb/issues/268

    My specific problem was related to hotplug events, but I expect that other things will go wrong for you too.

    The reason that this is less obvious in your scenario is because they are probably doing some setup / init when the library loads (how kind of them), rather than when you start to use it.

    As pointed out by @duskwuff, there is another answer here: https://stackoverflow.com/a/35186414/149341


    I've just done some playing, follow along below:

    cd $(mktemp -d)
    curl http://www.ftdichip.com/Drivers/D2XX/Linux/libftd2xx-x86_64-1.3.6.tgz | tar -xvz
    

    Put this in test.c:

    #include <stdio.h>
    #include "ftd2xx.h"
    
    int main(void) {
        int num_devs;
    
        fprintf(stderr, "in to main()\n");
    
        FT_STATUS ft_status = FT_CreateDeviceInfoList(&num_devs);
        fprintf(stderr, "FT_CreateDeviceInfoList() returned: %d\n", ft_status);
    
        fprintf(stderr, "out of main()\n");
    
        return 0;
    }
    

    Compile:

    gcc test.c -o test -g -I release -L release/build -lftd2xx -ldl -lpthread
    

    Now in gdb:

    [...]
    Reading symbols from test...done.
    (gdb) b libusb_init
    Breakpoint 1 at 0x40cd20
    (gdb) start
    Temporary breakpoint 2 at 0x401d75: file test.c, line 7.
    Starting program: /tmp/tmp.jJpBNywVzB/test 
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    
    Breakpoint 1, 0x000000000040cd20 in libusb_init ()
    (gdb) bt
    #0  0x000000000040cd20 in libusb_init ()
    #1  0x0000000000401f36 in my_init ()
    #2  0x000000000041a05d in __libc_csu_init ()
    #3  0x00007ffff7614ed5 in __libc_start_main (main=0x401d6d <main>, argc=1, argv=0x7fffffffe228, init=0x41a010 <__libc_csu_init>, fini=<optimised out>, rtld_fini=<optimised out>, stack_end=0x7fffffffe218)
        at libc-start.c:246
    #4  0x0000000000401ca9 in _start ()
    (gdb)
    

    It hit the breakpoint on libusb_init() before even getting to main(), called from their my_init().