Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Don't understand why a code snippet from APUE unlinks a file attached to the client unix domain socket

The book defines 3 customized functions:

int serv_listen(const char *name);
//Returns: file descriptor to listen on if OK, negative value on error

int serv_accept(int listenfd, uid_t *uidptr);
//Returns: new file descriptor if OK, negative value on error

int cli_conn(const char *name);
//Returns: file descriptor if OK, negative value on error

The serv_accept function (Figure 17.9) is used by a server to wait for a client’s connect request to arrive. When one arrives, the system automatically creates a new UNIX domain socket, connects it to the client’s socket, and returns the new socket to the server. Additionally, the effective user ID of the client is stored in the memory to which uidptr points.


serv_accept function code and description:

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <errno.h>

#define STALE   30  /* client's name can't be older than this (sec) */

/*
 * Wait for a client connection to arrive, and accept it.
 * We also obtain the client's user ID from the pathname
 * that it must bind before calling us.
 * Returns new fd if all OK, <0 on error
 */
int
serv_accept(int listenfd, uid_t *uidptr)
{
    int                 clifd, err, rval;
    socklen_t           len;
    time_t              staletime;
    struct sockaddr_un  un;
    struct stat         statbuf;
    char                *name;

    /* allocate enough space for longest name plus terminating null */
    if ((name = malloc(sizeof(un.sun_path + 1))) == NULL)
        return(-1);
    len = sizeof(un);
    if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
        free(name);
        return(-2);     /* often errno=EINTR, if signal caught */
    }

    /* obtain the client's uid from its calling address */
    len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
    memcpy(name, un.sun_path, len);
    name[len] = 0;          /* null terminate */
    if (stat(name, &statbuf) < 0) {
        rval = -3;
        goto errout;
    }

#ifdef  S_ISSOCK    /* not defined for SVR4 */
    if (S_ISSOCK(statbuf.st_mode) == 0) {
        rval = -4;      /* not a socket */
        goto errout;
    }
#endif

    if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
        (statbuf.st_mode & S_IRWXU) != S_IRWXU) {
          rval = -5;    /* is not rwx------ */
          goto errout;
    }

    staletime = time(NULL) - STALE;
    if (statbuf.st_atime < staletime ||
        statbuf.st_ctime < staletime ||
        statbuf.st_mtime < staletime) {
          rval = -6;    /* i-node is too old */
          goto errout;
    }

    if (uidptr != NULL)
        *uidptr = statbuf.st_uid;   /* return uid of caller */
    unlink(name);       /* we're done with pathname now */
    free(name);
    return(clifd);

errout:
    err = errno;
    close(clifd);
    free(name);
    errno = err;
    return(rval);
}

... Then we call stat to verify that the pathname is indeed a socket and that the permissions allow only user-read, user-write, and user-execute. We also verify that the three times associated with the socket are no older than 30 seconds.

If all these checks are OK, we assume that the identity of the client (its effective user ID) is the owner of the socket.

Why does the server code unlink(name) the file attached to the client's socket?

Other 2 functions code are provided via a link:

https://wandbox.org/permlink/jq5BajJYLgoh4yO6

like image 390
Rick Avatar asked Dec 04 '25 00:12

Rick


1 Answers

Why does the server code unlink(name) the file attached to the client's socket?

It's more accurate to say that the server is deleting the filepath attached to the client's socket. Or more colloquially, the client's socket's name.

Recall that unlink() does not delete named objects which are currently open in some process; the client's socket is presumably still open in the client, so unlink(name) doesn't yet delete the socket. Rather, it ensures that the socket will be deleted when it is no longer being used by a running process.

What it does do immediately is free up the name, so that the name can be reused with a different socket.

So why do that? Mostly so that the filesystem doesn't fill up with zombie socket names. It doesn't help the current client reuse the name (for example to connect to a different service) because the client anyway unlinks the name before attempting to use it. But the zombie name could be a problem for a different future client process with a different uid which happens to be assigned the same pid. That future process might not have sufficient permissions to unlink the name, in which case it will end up not being able to use this IPC mechanism (at least with this library).

Ok, so why is it unlinked by the server? The server makes use of the filepath for the stat call, and the client has no way of knowing when that happens. Since it's basically a good idea to unlink the name as soon as possible, it's better in this case for the server to unlink the name; it knows when it no longer needs the name.

Of course, the code as presented is not perfect. There are execution paths which will result in some names not being unlinked (for example, if the server process crashes at a bad time). But these should be rare. Experience shows that clients crash much more often than servers.

like image 68
rici Avatar answered Dec 06 '25 14:12

rici



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!