Mac OS X Internals: A Systems Approach

9.11. File Descriptor Passing

On a Unix system, a file descriptor is an integer that represents an open file in a process.[15] Each file descriptor is an index into the process's kernel-resident file descriptor table. The descriptor is local to the process in that it is meaningful only in the process that acquired the descriptorsay, by opening a file. In particular, a process A cannot access a file that is open in another process B by simply using the value of the descriptor representing that file in B.

[15] Section 11.5 discusses the kernel handling of file descriptors.

Many Unix systems support sending file descriptors from one process to another, unrelated process over an AF_LOCAL socket. Mac OS X also provides this IPC mechanism. Figure 941 shows details of the program-visible message buffer data structure involved in sending one or more file descriptors through the sendmsg() system call.

Figure 941. Program-visible data structures involved in file descriptor passing

The msghdr structure encapsulates several parameters to sendmsg() and recvmsg(). It can contain a pointer to a control buffer, which is ancillary data laid out as a control message structure consisting of a header (struct cmsghdr) and data (immediately following the header). In our case, the data is a file descriptor. Note that we have shown the msg_control field to point to a control buffer with one control message. In theory, the buffer could contain multiple control messages, with msg_controllen adjusted accordingly. The control buffer would then be a sequence of cmsghdr structures, each containing its length. The Mac OS X implementation supports only one control message per control buffer.

Protocol processing for sendmsg() [bsd/kern/uipc_syscalls.c] eventually results in a call to uipc_send() [bsd/kern/uipc_usrreq.c], which is passed a pointer to a control mbuf if the original call to sendmsg() contained a valid control buffer pointer. If so, uipc_send() calls unp_internalize() [bsd/kern/uipc_usrreq.c] to internalize the ancillary datait iterates over the list of file descriptors in the buffer and converts each to its corresponding file structure (struct fileglob [bsd/sys/file_internal.h]). unp_internalize() requires that the cmsg_level and cmsg_type fields be set to SOL_SOCKET and SCM_RIGHTS, respectively. SCM_RIGHTS specifies that the control message data contains access rights.

When such a message is received, the list of file structures is externalized by a call to unp_externalize() [bsd/kern/uipc_usrreq.c]; for each file structure, a local file descriptor in the receiving process is consumed to represent an open file. After a successful recvmsg(), the receiver can use such file descriptors normally.

File descriptor passing has many conceptual parallels with passing port rights in a Mach IPC message.

Let us look at a programming example of descriptor passing. We will write a descriptor-passing server that serves a given file over an AF_LOCAL socket connection. A client will connect to this server, receive the file descriptor, and then use the descriptor to read the file. The socket's address and the format of the control message are specified in a common header file that will be shared between server and client implementations. Figures 942, 943, and 944 show the common header file, the server's implementation, and the client's implementation.

Figure 942. Common header file for the descriptor-passing client-server implementation

// fd_common.h #ifndef _FD_COMMON_H_ #define _FD_COMMON_H_ #include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #define SERVER_NAME "/tmp/.fdserver" typedef union { struct cmsghdr cmsghdr; u_char msg_control[CMSG_SPACE(sizeof(int))]; } cmsghdr_msg_control_t; #endif // _FD_COMMON_H_

Figure 943. Implementation of the descriptor-passing server

// fd_sender.c #include "fd_common.h" int setup_server(const char *name); int send_fd_using_sockfd(int fd, int sockfd); int setup_server(const char *name) { int sockfd, len; struct sockaddr_un server_unix_addr; if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket"); return sockfd; } unlink(name); bzero((char *)&server_unix_addr, sizeof(server_unix_addr)); server_unix_addr.sun_family = AF_LOCAL; strcpy(server_unix_addr.sun_path, name); len = strlen(name) + 1; len += sizeof(server_unix_addr.sun_family); if (bind(sockfd, (struct sockaddr *)&server_unix_addr, len) < 0) { close(sockfd); return -1; } return sockfd; } int send_fd_using_sockfd(int fd, int sockfd) { ssize_t ret; struct iovec iovec[1]; struct msghdr msg; struct cmsghdr *cmsghdrp; cmsghdr_msg_control_t cmsghdr_msg_control; iovec[0].iov_base = ""; iovec[0].iov_len = 1; msg.msg_name = (caddr_t)0; // address (optional) msg.msg_namelen = 0; // size of address msg.msg_iov = iovec; // scatter/gather array msg.msg_iovlen = 1; // members in msg.msg_iov msg.msg_control = cmsghdr_msg_control.msg_control; // ancillary data // ancillary data buffer length msg.msg_controllen = sizeof(cmsghdr_msg_control.msg_control); msg.msg_flags = 0; // flags on received message // CMSG_FIRSTHDR() returns a pointer to the first cmsghdr structure in // the ancillary data associated with the given msghdr structure cmsghdrp = CMSG_FIRSTHDR(&msg); cmsghdrp->cmsg_len = CMSG_LEN(sizeof(int)); // data byte count cmsghdrp->cmsg_level = SOL_SOCKET; // originating protocol cmsghdrp->cmsg_type = SCM_RIGHTS; // protocol-specified type // CMSG_DATA() returns a pointer to the data array associated with // the cmsghdr structure pointed to by cmsghdrp *((int *)CMSG_DATA(cmsghdrp)) = fd; if ((ret = sendmsg(sockfd, &msg, 0)) < 0) { perror("sendmsg"); return ret; } return 0; } int main(int argc, char **argv) { int fd, sockfd; int csockfd; socklen_t len; struct sockaddr_un client_unix_addr; if (argc != 2) { fprintf(stderr, "usage: %s <file path>\n", argv[0]); exit(1); } if ((sockfd = setup_server(SERVER_NAME)) < 0) { fprintf(stderr, "failed to set up server\n"); exit(1); } if ((fd = open(argv[1], O_RDONLY)) < 0) { perror("open"); close(sockfd); exit(1); } listen(sockfd, 0); for (;;) { len = sizeof(client_unix_addr); csockfd = accept(sockfd, (struct sockaddr *)&client_unix_addr, &len); if (csockfd < 0) { perror("accept"); close(sockfd); exit(1); } if ((send_fd_using_sockfd(fd, csockfd) < 0)) fprintf(stderr, "failed to send file descriptor (fd = %d)\n", fd); else fprintf(stderr, "file descriptor sent (fd = %d)\n", fd); close(sockfd); close(csockfd); break; } exit(0); }

Figure 944. Implementation of the descriptor-passing client

// fd_receiver.c #include "fd_common.h" int receive_fd_using_sockfd(int *fd, int sockfd); int receive_fd_using_sockfd(int *fd, int sockfd) { ssize_t ret; u_char c; int errcond = 0; struct iovec iovec[1]; struct msghdr msg; struct cmsghdr *cmsghdrp; cmsghdr_msg_control_t cmsghdr_msg_control; iovec[0].iov_base = &c; iovec[0].iov_len = 1; msg.msg_name = (caddr_t)0; msg.msg_namelen = 0; msg.msg_iov = iovec; msg.msg_iovlen = 1; msg.msg_control = cmsghdr_msg_control.msg_control; msg.msg_controllen = sizeof(cmsghdr_msg_control.msg_control); msg.msg_flags = 0; if ((ret = recvmsg(sockfd, &msg, 0)) <= 0) { perror("recvmsg"); return ret; } cmsghdrp = CMSG_FIRSTHDR(&msg); if (cmsghdrp == NULL) { *fd = -1; return ret; } if (cmsghdrp->cmsg_len != CMSG_LEN(sizeof(int))) errcond++; if (cmsghdrp->cmsg_level != SOL_SOCKET) errcond++; if (cmsghdrp->cmsg_type != SCM_RIGHTS) errcond++; if (errcond) { fprintf(stderr, "%d errors in received message\n", errcond); *fd = -1; } else *fd = *((int *)CMSG_DATA(cmsghdrp)); return ret; } int main(int argc, char **argv) { char buf[512]; int fd = -1, sockfd, len, ret; struct sockaddr_un server_unix_addr; bzero((char *)&server_unix_addr, sizeof(server_unix_addr)); strcpy(server_unix_addr.sun_path, SERVER_NAME); server_unix_addr.sun_family = AF_LOCAL; len = strlen(SERVER_NAME) + 1; len += sizeof(server_unix_addr.sun_family); if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(1); } if (connect(sockfd, (struct sockaddr *)&server_unix_addr, len) < 0) { perror("connect"); close(sockfd); exit(1); } ret = receive_fd_using_sockfd(&fd, sockfd); if ((ret < 0) || (fd < 0)) { fprintf(stderr, "failed to receive file descriptor\n"); close(sockfd); exit(1); } printf("received file descriptor (fd = %d)\n", fd); if ((ret = read(fd, buf, 512)) > 0) write(1, buf, ret); exit(0); }

Let us now test our descriptor-passing client and server.

$ gcc -Wall -o fd_sender fd_sender.c $ gcc -Wall -o fd_receiver fd_receiver.c $ echo "Hello, Descriptor" > /tmp/message.txt $ ./fd_sender /tmp/message.txt ... $ ./fd_receiver# from another shell prompt received file descriptor (fd = 10) Hello, Descriptor

Категории