9.11. File Descriptor Passing
On a Unix system, a file descriptor is an integer that represents an
open
file in a process.
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.
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.
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
|