通过 Linux 套接字发送文件描述符
Sending file descriptor by Linux socket
我正在尝试通过 linux 套接字发送一些文件描述符,但它不起作用。我究竟做错了什么?应该如何调试这样的东西?我试着把 perror() 放在任何可能的地方,但他们声称一切正常。这是我写的:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
void wyslij(int socket, int fd) // send fd by socket
{
struct msghdr msg = {0};
char buf[CMSG_SPACE(sizeof fd)];
msg.msg_control = buf;
msg.msg_controllen = sizeof buf;
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof fd);
*((int *) CMSG_DATA(cmsg)) = fd;
msg.msg_controllen = cmsg->cmsg_len; // why does example from man need it? isn't it redundant?
sendmsg(socket, &msg, 0);
}
int odbierz(int socket) // receive fd from socket
{
struct msghdr msg = {0};
recvmsg(socket, &msg, 0);
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
unsigned char * data = CMSG_DATA(cmsg);
int fd = *((int*) data); // here program stops, probably with segfault
return fd;
}
int main()
{
int sv[2];
socketpair(AF_UNIX, SOCK_DGRAM, 0, sv);
int pid = fork();
if (pid > 0) // in parent
{
close(sv[1]);
int sock = sv[0];
int fd = open("./z7.c", O_RDONLY);
wyslij(sock, fd);
close(fd);
}
else // in child
{
close(sv[0]);
int sock = sv[1];
sleep(0.5);
int fd = odbierz(sock);
}
}
史蒂文斯(等人)UNIX® Network Programming, Vol 1: The Sockets Networking API describes the process of transferring file descriptors between processes in Chapter 15 Unix Domain Protocols and specifically §15.7 Passing Descriptors. It's fiddly to describe in full, but it must be done on a Unix domain socket (AF_UNIX
or AF_LOCAL
), and the sender process uses sendmsg()
while the receiver uses recvmsg()
.
我从问题中得到了这个经过轻微修改(和检测)的代码版本,可以在 Mac OS X 10.10.1 Yosemite 和 GCC 4.9.1 上为我工作:
#include "stderr.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static
void wyslij(int socket, int fd) // send fd by socket
{
struct msghdr msg = { 0 };
char buf[CMSG_SPACE(sizeof(fd))];
memset(buf, '[=10=]', sizeof(buf));
struct iovec io = { .iov_base = "ABC", .iov_len = 3 };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
*((int *) CMSG_DATA(cmsg)) = fd;
msg.msg_controllen = CMSG_SPACE(sizeof(fd));
if (sendmsg(socket, &msg, 0) < 0)
err_syserr("Failed to send message\n");
}
static
int odbierz(int socket) // receive fd from socket
{
struct msghdr msg = {0};
char m_buffer[256];
struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
char c_buffer[256];
msg.msg_control = c_buffer;
msg.msg_controllen = sizeof(c_buffer);
if (recvmsg(socket, &msg, 0) < 0)
err_syserr("Failed to receive message\n");
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
unsigned char * data = CMSG_DATA(cmsg);
err_remark("About to extract fd\n");
int fd = *((int*) data);
err_remark("Extracted fd %d\n", fd);
return fd;
}
int main(int argc, char **argv)
{
const char *filename = "./z7.c";
err_setarg0(argv[0]);
err_setlogopts(ERR_PID);
if (argc > 1)
filename = argv[1];
int sv[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
err_syserr("Failed to create Unix-domain socket pair\n");
int pid = fork();
if (pid > 0) // in parent
{
err_remark("Parent at work\n");
close(sv[1]);
int sock = sv[0];
int fd = open(filename, O_RDONLY);
if (fd < 0)
err_syserr("Failed to open file %s for reading\n", filename);
wyslij(sock, fd);
close(fd);
nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);
err_remark("Parent exits\n");
}
else // in child
{
err_remark("Child at play\n");
close(sv[0]);
int sock = sv[1];
nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);
int fd = odbierz(sock);
printf("Read %d!\n", fd);
char buffer[256];
ssize_t nbytes;
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
write(1, buffer, nbytes);
printf("Done!\n");
close(fd);
}
return 0;
}
经过检测但未修复的原始代码版本的输出是:
$ ./fd-passing
fd-passing: pid=1391: Parent at work
fd-passing: pid=1391: Failed to send message
error (40) Message too long
fd-passing: pid=1392: Child at play
$ fd-passing: pid=1392: Failed to receive message
error (40) Message too long
注意 parent 在 child 之前完成,所以提示出现在输出的中间。
'fixed' 代码的输出是:
$ ./fd-passing
fd-passing: pid=1046: Parent at work
fd-passing: pid=1048: Child at play
fd-passing: pid=1048: About to extract fd
fd-passing: pid=1048: Extracted fd 3
Read 3!
This is the file z7.c.
It isn't very interesting.
It isn't even C code.
But it is used by the fd-passing program to demonstrate that file
descriptors can indeed be passed between sockets on occasion.
Done!
fd-passing: pid=1046: Parent exits
$
主要的重大变化是将 struct iovec
添加到两个函数中 struct msghdr
中的数据,并在接收函数 (odbierz()
) 中提供 space控制消息。我报告了调试的中间步骤,我向 parent 提供了 struct iovec
并且 parent 的 "message too long" 错误被删除了。为了证明它正在工作(传递了一个文件描述符),我添加了代码以从传递的文件描述符中读取和打印文件。原始代码有 sleep(0.5)
但由于 sleep()
采用无符号整数,这相当于不睡觉。我使用 C99 复合文字让 child 休眠 0.5 秒。 parent 休眠 1.5 秒,以便 child 的输出在 parent 退出之前完成。我也可以使用 wait()
或 waitpid()
,但懒得这样做。
我没有回去检查是否所有添加都是必要的。
"stderr.h"
header 声明了 err_*()
函数。这是我编写的代码(1987 年之前的第一个版本),用于简洁地报告错误。 err_setlogopts(ERR_PID)
调用为所有消息加上 PID 前缀。对于时间戳,err_setlogopts(ERR_PID|ERR_STAMP)
也可以。
对齐问题
Nominal Animal suggests in a comment:
May I suggest you modify the code to copy the descriptor int
using memcpy()
instead of accessing the data directly? It is not necessarily correctly aligned — which is why the man page example also uses memcpy()
— and there are many Linux architectures where unaligned int
access causes problems (up to SIGBUS signal killing the process).
而且不仅是 Linux 架构:SPARC 和 Power 都需要对齐的数据,而且通常分别是 运行 Solaris 和 AIX。曾几何时,DEC Alpha 也需要这样做,但现在很少有人在现场看到它们了。
手册页cmsg(3)
中与此相关的代码是:
struct msghdr msg = {0};
struct cmsghdr *cmsg;
int myfds[NUM_FD]; /* Contains the file descriptors to pass. */
char buf[CMSG_SPACE(sizeof myfds)]; /* ancillary data buffer */
int *fdptr;
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(int) * NUM_FD);
/* Initialize the payload: */
fdptr = (int *) CMSG_DATA(cmsg);
memcpy(fdptr, myfds, NUM_FD * sizeof(int));
/* Sum of the length of all control messages in the buffer: */
msg.msg_controllen = CMSG_SPACE(sizeof(int) * NUM_FD);
fdptr
的赋值似乎假设 CMSG_DATA(cmsg)
已充分对齐以转换为 int *
,而 memcpy()
的使用基于以下假设 NUM_FD
不只是 1。话虽如此,它应该指向数组 buf
,并且可能没有像 Nominal Animal 所建议的那样充分对齐,所以在我看来 fdptr
只是一个闯入者,如果使用示例会更好:
memcpy(CMSG_DATA(cmsg), myfds, NUM_FD * sizeof(int));
然后在接收端进行相反的处理就可以了。该程序只传递一个文件描述符,因此代码可修改为:
memmove(CMSG_DATA(cmsg), &fd, sizeof(fd)); // Send
memmove(&fd, CMSG_DATA(cmsg), sizeof(fd)); // Receive
I also seem to recall historical issues on various OSes w.r.t. ancillary data with no normal payload data, avoided by sending at least one dummy byte too, but I cannot find any references to verify, so I might remember wrong.
鉴于 Mac OS X(具有 Darwin/BSD 基础)需要至少一个 struct iovec
,即使它描述了 zero-length 消息,我愿意相信上面显示的代码(包括一条 3 字节消息)是朝着正确的总体方向迈出的良好一步。该消息可能应该是单个空字节而不是 3 个字母。
我修改了代码,如下所示。它使用 memmove()
将文件描述符复制到 cmsg
缓冲区或从 cmsg
缓冲区复制文件描述符。它传输单个消息字节,这是一个空字节。
在将文件描述符传递给 child 之前,它还有 parent 进程读取(最多)32 个字节的文件。 child 从 parent 停止的地方继续阅读。这表明传输的文件描述符包括文件偏移量。
接收方在将 cmsg
视为文件描述符传递消息之前应对其进行更多验证。
#include "stderr.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static
void wyslij(int socket, int fd) // send fd by socket
{
struct msghdr msg = { 0 };
char buf[CMSG_SPACE(sizeof(fd))];
memset(buf, '[=16=]', sizeof(buf));
/* On Mac OS X, the struct iovec is needed, even if it points to minimal data */
struct iovec io = { .iov_base = "", .iov_len = 1 };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));
msg.msg_controllen = CMSG_SPACE(sizeof(fd));
if (sendmsg(socket, &msg, 0) < 0)
err_syserr("Failed to send message\n");
}
static
int odbierz(int socket) // receive fd from socket
{
struct msghdr msg = {0};
/* On Mac OS X, the struct iovec is needed, even if it points to minimal data */
char m_buffer[1];
struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
char c_buffer[256];
msg.msg_control = c_buffer;
msg.msg_controllen = sizeof(c_buffer);
if (recvmsg(socket, &msg, 0) < 0)
err_syserr("Failed to receive message\n");
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
err_remark("About to extract fd\n");
int fd;
memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));
err_remark("Extracted fd %d\n", fd);
return fd;
}
int main(int argc, char **argv)
{
const char *filename = "./z7.c";
err_setarg0(argv[0]);
err_setlogopts(ERR_PID);
if (argc > 1)
filename = argv[1];
int sv[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
err_syserr("Failed to create Unix-domain socket pair\n");
int pid = fork();
if (pid > 0) // in parent
{
err_remark("Parent at work\n");
close(sv[1]);
int sock = sv[0];
int fd = open(filename, O_RDONLY);
if (fd < 0)
err_syserr("Failed to open file %s for reading\n", filename);
/* Read some data to demonstrate that file offset is passed */
char buffer[32];
int nbytes = read(fd, buffer, sizeof(buffer));
if (nbytes > 0)
err_remark("Parent read: [[%.*s]]\n", nbytes, buffer);
wyslij(sock, fd);
close(fd);
nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);
err_remark("Parent exits\n");
}
else // in child
{
err_remark("Child at play\n");
close(sv[0]);
int sock = sv[1];
nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);
int fd = odbierz(sock);
printf("Read %d!\n", fd);
char buffer[256];
ssize_t nbytes;
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
write(1, buffer, nbytes);
printf("Done!\n");
close(fd);
}
return 0;
}
和样本 运行:
$ ./fd-passing
fd-passing: pid=8000: Parent at work
fd-passing: pid=8000: Parent read: [[This is the file z7.c.
It isn't ]]
fd-passing: pid=8001: Child at play
fd-passing: pid=8001: About to extract fd
fd-passing: pid=8001: Extracted fd 3
Read 3!
very interesting.
It isn't even C code.
But it is used by the fd-passing program to demonstrate that file
descriptors can indeed be passed between sockets on occasion.
And, with the fully working code, it does indeed seem to work.
Extended testing would have the parent code read part of the file, and
then demonstrate that the child codecontinues where the parent left off.
That has not been coded, though.
Done!
fd-passing: pid=8000: Parent exits
$
我正在尝试通过 linux 套接字发送一些文件描述符,但它不起作用。我究竟做错了什么?应该如何调试这样的东西?我试着把 perror() 放在任何可能的地方,但他们声称一切正常。这是我写的:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
void wyslij(int socket, int fd) // send fd by socket
{
struct msghdr msg = {0};
char buf[CMSG_SPACE(sizeof fd)];
msg.msg_control = buf;
msg.msg_controllen = sizeof buf;
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof fd);
*((int *) CMSG_DATA(cmsg)) = fd;
msg.msg_controllen = cmsg->cmsg_len; // why does example from man need it? isn't it redundant?
sendmsg(socket, &msg, 0);
}
int odbierz(int socket) // receive fd from socket
{
struct msghdr msg = {0};
recvmsg(socket, &msg, 0);
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
unsigned char * data = CMSG_DATA(cmsg);
int fd = *((int*) data); // here program stops, probably with segfault
return fd;
}
int main()
{
int sv[2];
socketpair(AF_UNIX, SOCK_DGRAM, 0, sv);
int pid = fork();
if (pid > 0) // in parent
{
close(sv[1]);
int sock = sv[0];
int fd = open("./z7.c", O_RDONLY);
wyslij(sock, fd);
close(fd);
}
else // in child
{
close(sv[0]);
int sock = sv[1];
sleep(0.5);
int fd = odbierz(sock);
}
}
史蒂文斯(等人)UNIX® Network Programming, Vol 1: The Sockets Networking API describes the process of transferring file descriptors between processes in Chapter 15 Unix Domain Protocols and specifically §15.7 Passing Descriptors. It's fiddly to describe in full, but it must be done on a Unix domain socket (AF_UNIX
or AF_LOCAL
), and the sender process uses sendmsg()
while the receiver uses recvmsg()
.
我从问题中得到了这个经过轻微修改(和检测)的代码版本,可以在 Mac OS X 10.10.1 Yosemite 和 GCC 4.9.1 上为我工作:
#include "stderr.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static
void wyslij(int socket, int fd) // send fd by socket
{
struct msghdr msg = { 0 };
char buf[CMSG_SPACE(sizeof(fd))];
memset(buf, '[=10=]', sizeof(buf));
struct iovec io = { .iov_base = "ABC", .iov_len = 3 };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
*((int *) CMSG_DATA(cmsg)) = fd;
msg.msg_controllen = CMSG_SPACE(sizeof(fd));
if (sendmsg(socket, &msg, 0) < 0)
err_syserr("Failed to send message\n");
}
static
int odbierz(int socket) // receive fd from socket
{
struct msghdr msg = {0};
char m_buffer[256];
struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
char c_buffer[256];
msg.msg_control = c_buffer;
msg.msg_controllen = sizeof(c_buffer);
if (recvmsg(socket, &msg, 0) < 0)
err_syserr("Failed to receive message\n");
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
unsigned char * data = CMSG_DATA(cmsg);
err_remark("About to extract fd\n");
int fd = *((int*) data);
err_remark("Extracted fd %d\n", fd);
return fd;
}
int main(int argc, char **argv)
{
const char *filename = "./z7.c";
err_setarg0(argv[0]);
err_setlogopts(ERR_PID);
if (argc > 1)
filename = argv[1];
int sv[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
err_syserr("Failed to create Unix-domain socket pair\n");
int pid = fork();
if (pid > 0) // in parent
{
err_remark("Parent at work\n");
close(sv[1]);
int sock = sv[0];
int fd = open(filename, O_RDONLY);
if (fd < 0)
err_syserr("Failed to open file %s for reading\n", filename);
wyslij(sock, fd);
close(fd);
nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);
err_remark("Parent exits\n");
}
else // in child
{
err_remark("Child at play\n");
close(sv[0]);
int sock = sv[1];
nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);
int fd = odbierz(sock);
printf("Read %d!\n", fd);
char buffer[256];
ssize_t nbytes;
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
write(1, buffer, nbytes);
printf("Done!\n");
close(fd);
}
return 0;
}
经过检测但未修复的原始代码版本的输出是:
$ ./fd-passing
fd-passing: pid=1391: Parent at work
fd-passing: pid=1391: Failed to send message
error (40) Message too long
fd-passing: pid=1392: Child at play
$ fd-passing: pid=1392: Failed to receive message
error (40) Message too long
注意 parent 在 child 之前完成,所以提示出现在输出的中间。
'fixed' 代码的输出是:
$ ./fd-passing
fd-passing: pid=1046: Parent at work
fd-passing: pid=1048: Child at play
fd-passing: pid=1048: About to extract fd
fd-passing: pid=1048: Extracted fd 3
Read 3!
This is the file z7.c.
It isn't very interesting.
It isn't even C code.
But it is used by the fd-passing program to demonstrate that file
descriptors can indeed be passed between sockets on occasion.
Done!
fd-passing: pid=1046: Parent exits
$
主要的重大变化是将 struct iovec
添加到两个函数中 struct msghdr
中的数据,并在接收函数 (odbierz()
) 中提供 space控制消息。我报告了调试的中间步骤,我向 parent 提供了 struct iovec
并且 parent 的 "message too long" 错误被删除了。为了证明它正在工作(传递了一个文件描述符),我添加了代码以从传递的文件描述符中读取和打印文件。原始代码有 sleep(0.5)
但由于 sleep()
采用无符号整数,这相当于不睡觉。我使用 C99 复合文字让 child 休眠 0.5 秒。 parent 休眠 1.5 秒,以便 child 的输出在 parent 退出之前完成。我也可以使用 wait()
或 waitpid()
,但懒得这样做。
我没有回去检查是否所有添加都是必要的。
"stderr.h"
header 声明了 err_*()
函数。这是我编写的代码(1987 年之前的第一个版本),用于简洁地报告错误。 err_setlogopts(ERR_PID)
调用为所有消息加上 PID 前缀。对于时间戳,err_setlogopts(ERR_PID|ERR_STAMP)
也可以。
对齐问题
Nominal Animal suggests in a comment:
May I suggest you modify the code to copy the descriptor
int
usingmemcpy()
instead of accessing the data directly? It is not necessarily correctly aligned — which is why the man page example also usesmemcpy()
— and there are many Linux architectures where unalignedint
access causes problems (up to SIGBUS signal killing the process).
而且不仅是 Linux 架构:SPARC 和 Power 都需要对齐的数据,而且通常分别是 运行 Solaris 和 AIX。曾几何时,DEC Alpha 也需要这样做,但现在很少有人在现场看到它们了。
手册页cmsg(3)
中与此相关的代码是:
struct msghdr msg = {0};
struct cmsghdr *cmsg;
int myfds[NUM_FD]; /* Contains the file descriptors to pass. */
char buf[CMSG_SPACE(sizeof myfds)]; /* ancillary data buffer */
int *fdptr;
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(int) * NUM_FD);
/* Initialize the payload: */
fdptr = (int *) CMSG_DATA(cmsg);
memcpy(fdptr, myfds, NUM_FD * sizeof(int));
/* Sum of the length of all control messages in the buffer: */
msg.msg_controllen = CMSG_SPACE(sizeof(int) * NUM_FD);
fdptr
的赋值似乎假设 CMSG_DATA(cmsg)
已充分对齐以转换为 int *
,而 memcpy()
的使用基于以下假设 NUM_FD
不只是 1。话虽如此,它应该指向数组 buf
,并且可能没有像 Nominal Animal 所建议的那样充分对齐,所以在我看来 fdptr
只是一个闯入者,如果使用示例会更好:
memcpy(CMSG_DATA(cmsg), myfds, NUM_FD * sizeof(int));
然后在接收端进行相反的处理就可以了。该程序只传递一个文件描述符,因此代码可修改为:
memmove(CMSG_DATA(cmsg), &fd, sizeof(fd)); // Send
memmove(&fd, CMSG_DATA(cmsg), sizeof(fd)); // Receive
I also seem to recall historical issues on various OSes w.r.t. ancillary data with no normal payload data, avoided by sending at least one dummy byte too, but I cannot find any references to verify, so I might remember wrong.
鉴于 Mac OS X(具有 Darwin/BSD 基础)需要至少一个 struct iovec
,即使它描述了 zero-length 消息,我愿意相信上面显示的代码(包括一条 3 字节消息)是朝着正确的总体方向迈出的良好一步。该消息可能应该是单个空字节而不是 3 个字母。
我修改了代码,如下所示。它使用 memmove()
将文件描述符复制到 cmsg
缓冲区或从 cmsg
缓冲区复制文件描述符。它传输单个消息字节,这是一个空字节。
在将文件描述符传递给 child 之前,它还有 parent 进程读取(最多)32 个字节的文件。 child 从 parent 停止的地方继续阅读。这表明传输的文件描述符包括文件偏移量。
接收方在将 cmsg
视为文件描述符传递消息之前应对其进行更多验证。
#include "stderr.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static
void wyslij(int socket, int fd) // send fd by socket
{
struct msghdr msg = { 0 };
char buf[CMSG_SPACE(sizeof(fd))];
memset(buf, '[=16=]', sizeof(buf));
/* On Mac OS X, the struct iovec is needed, even if it points to minimal data */
struct iovec io = { .iov_base = "", .iov_len = 1 };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));
msg.msg_controllen = CMSG_SPACE(sizeof(fd));
if (sendmsg(socket, &msg, 0) < 0)
err_syserr("Failed to send message\n");
}
static
int odbierz(int socket) // receive fd from socket
{
struct msghdr msg = {0};
/* On Mac OS X, the struct iovec is needed, even if it points to minimal data */
char m_buffer[1];
struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
char c_buffer[256];
msg.msg_control = c_buffer;
msg.msg_controllen = sizeof(c_buffer);
if (recvmsg(socket, &msg, 0) < 0)
err_syserr("Failed to receive message\n");
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
err_remark("About to extract fd\n");
int fd;
memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));
err_remark("Extracted fd %d\n", fd);
return fd;
}
int main(int argc, char **argv)
{
const char *filename = "./z7.c";
err_setarg0(argv[0]);
err_setlogopts(ERR_PID);
if (argc > 1)
filename = argv[1];
int sv[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
err_syserr("Failed to create Unix-domain socket pair\n");
int pid = fork();
if (pid > 0) // in parent
{
err_remark("Parent at work\n");
close(sv[1]);
int sock = sv[0];
int fd = open(filename, O_RDONLY);
if (fd < 0)
err_syserr("Failed to open file %s for reading\n", filename);
/* Read some data to demonstrate that file offset is passed */
char buffer[32];
int nbytes = read(fd, buffer, sizeof(buffer));
if (nbytes > 0)
err_remark("Parent read: [[%.*s]]\n", nbytes, buffer);
wyslij(sock, fd);
close(fd);
nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);
err_remark("Parent exits\n");
}
else // in child
{
err_remark("Child at play\n");
close(sv[0]);
int sock = sv[1];
nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);
int fd = odbierz(sock);
printf("Read %d!\n", fd);
char buffer[256];
ssize_t nbytes;
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
write(1, buffer, nbytes);
printf("Done!\n");
close(fd);
}
return 0;
}
和样本 运行:
$ ./fd-passing
fd-passing: pid=8000: Parent at work
fd-passing: pid=8000: Parent read: [[This is the file z7.c.
It isn't ]]
fd-passing: pid=8001: Child at play
fd-passing: pid=8001: About to extract fd
fd-passing: pid=8001: Extracted fd 3
Read 3!
very interesting.
It isn't even C code.
But it is used by the fd-passing program to demonstrate that file
descriptors can indeed be passed between sockets on occasion.
And, with the fully working code, it does indeed seem to work.
Extended testing would have the parent code read part of the file, and
then demonstrate that the child codecontinues where the parent left off.
That has not been coded, though.
Done!
fd-passing: pid=8000: Parent exits
$