chetan kankotiya
chetan kankotiya

Reputation: 106

sharing of opened file descriptor between different processes

There is scenario where one process open serial port and configure it's terminal parameters. How to share this file descriptor to another two processes which will also pass data over serial port. is it possible to share opened file descriptor? if yes then, how is it possible?

EDIT: note one thing these three processes are not related processes (not parent/child processes)

Upvotes: 1

Views: 770

Answers (2)

Art
Art

Reputation: 20392

The easiest thing is to open and configure the file descriptor before forking off the other processes. This will save you the most headaches.

Assuming that is not possible, there is a way.

Create a unix domain socket. Make the main process listen on that socket. As soon as someone connects to it, send a cmsg with SCM_RIGHTS. On my linux system this is documented on the man page "cmsg(3)". On my MacOS (where I wrote the example code) I couldn't find where it's documented.

Now, in the processes that need a copy of the special file descriptor, open the unix domain socket read the cmsg and you now have the file descriptor.

Here's some example code. This is long because the whole process is quite verbose. What it will do is without sharing any file descriptors it will send the descriptor to the file '/tmp/magic' to the client and the client will write a message to it. The file descriptor passed is in the local variable magic in both the client and server.

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <err.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

#define SOCKET_NAME "/tmp/socket"

#define SPECIAL_FILE "/tmp/magic"

void
server(void)
{
    struct sockaddr_un sun = { 0 };
    socklen_t sunlen;
    int l, fd, magic;

    if ((magic = open(SPECIAL_FILE, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1)
        err(1, "open");

    if ((l = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
        err(1, "server socket");

    unlink(SOCKET_NAME);

    sun.sun_family = AF_UNIX;
    snprintf(sun.sun_path, sizeof(sun.sun_path), SOCKET_NAME);

    if (bind(l, (struct sockaddr *)&sun, sizeof(sun)) == -1)
        err(1, "bind");

    if (listen(l, 5) == -1)
        err(1, "listen");

    sunlen = sizeof(sun);

    while ((fd = accept(l, (struct sockaddr *)&sun, &sunlen)) != -1) {
        struct msghdr msg = { 0 };
        struct cmsghdr *cmsg;
        struct iovec iov;
        char buf[CMSG_SPACE(sizeof(magic))];

        iov.iov_base = (void *)"ok";
        iov.iov_len = 2;

        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = buf;
        msg.msg_controllen = sizeof(buf);
        cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(sizeof(magic));

        *((int *)(void *)CMSG_DATA(cmsg)) = magic;

        if (sendmsg(fd, &msg, 0) == -1)
            err(1, "sendmsg");
        close(fd);
    }

    close(l);
    unlink(SOCKET_NAME);
}

void
client(void)
{
    struct sockaddr_un sun = { 0 };
    struct msghdr msg = { 0 };
    struct cmsghdr *cmsg;
    struct iovec iov;
    int fd, magic;
    char cbuf[CMSG_SPACE(sizeof(int))];
    char buf[16];

    if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
        err(1, "socket");
    }

    sun.sun_family = AF_UNIX;
    snprintf(sun.sun_path, sizeof(sun.sun_path), SOCKET_NAME);

    if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
        err(1, "connect");
    }

    iov.iov_base = buf;
    iov.iov_len = sizeof(buf);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = cbuf;
    msg.msg_controllen = sizeof(cbuf);

    if (recvmsg(fd, &msg, 0) == -1)
        err(1, "recvmsg");

    cmsg = CMSG_FIRSTHDR(&msg);
    assert(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS);
    magic = *(int *)(void *)CMSG_DATA(cmsg);

    if (write(magic, "hello", strlen("hello")) != strlen("hello"))
        err(1, "write(magic)");

    close(magic);
    close(fd);
}

int
main(int argc, char **argv)
{
    switch (fork()) {
    case 0:
        sleep(2);       /* poor mans synchronization */
        printf("starting client\n");
        client();
        _exit(0);
        break;
    case -1:
        err(1, "fork");
    default:
        printf("starting server\n");
        server();
        exit(1);
    }
    return 1;
}

Some notes about the example:

  • It works on MacOS, I haven't tested it on other systems, but the same approach definitely works on *BSD and Linux. I have no experience with doing this on other systems. Allegedly this is POSIX, so it should work on more systems.
  • It shouldn't be necessary to include any payload with the message (so the iovec with "ok" shouldn't be necessary), but for some reason I couldn't get it to work without.
  • You want a better location and permissions on the socket, "/tmp/socket" is an awful hardcoded name on a real system.
  • From experience I know that the CMSG_* macros have in some operating systems caused alignment problems. I believe that I did everything right when allocating the buffers and all the magic casts back and forth, but YMMV on a strict alignment architecture. You may need to allocate the buffers with better alignment with malloc or even posix_memalign.

Upvotes: 3

doron
doron

Reputation: 28872

There is a way to do this using either pipes or a unix domain socket. The details can be found here.

Upvotes: 0

Related Questions