OILS / cpp / fanos_shared.c View on Github | oilshell.org

215 lines, 141 significant
1#include "cpp/fanos_shared.h"
2
3#include <assert.h>
4#include <errno.h>
5#include <stdarg.h> // va_list, etc.
6#include <stdio.h> // vfprintf
7#include <stdlib.h>
8#include <string.h>
9#include <sys/socket.h>
10#include <unistd.h>
11
12#define SIZEOF_FDS (sizeof(int) * FANOS_NUM_FDS)
13
14const char* kErrTooLarge = "Message too large";
15const char* kErrSolSocket = "Expected cmsg_level SOL_SOCKET";
16const char* kErrScmRights = "Expected cmsg_type SCM_RIGHTS";
17const char* kErrUnexpectedEof = "Unexpected EOF";
18const char* kErrMissingLength = "Expected netstring length";
19const char* kErrMissingColon = "Expected : after netstring length";
20const char* kErrMissingComma = "Expected ,";
21
22void fanos_send(int sock_fd, char* blob, int blob_len, const int* fds,
23 struct FanosError* err) {
24 char buf[10];
25 // snprintf() doesn't write more than 10 bytes, INCLUDING \0
26 // It the number of bytes it would have written, EXCLUDING \0
27 unsigned int full_length = snprintf(buf, 10, "%d:", blob_len);
28 if (full_length > sizeof(buf)) {
29 err->value_err = kErrTooLarge;
30 return;
31 }
32
33 if (write(sock_fd, buf, full_length) < 0) { // send '3:'
34 err->err_code = errno;
35 return;
36 }
37
38 // Example code adapted from 'man CMSG_LEN' on my machine. (Is this
39 // portable?)
40 //
41 // The APUE code only sends a single FD and doesn't use CMSG_SPACE.
42
43 // Set up the blob data
44 struct iovec iov = {0};
45 iov.iov_base = blob;
46 iov.iov_len = blob_len;
47
48 struct msghdr msg = {0};
49 msg.msg_iov = &iov;
50 msg.msg_iovlen = 1;
51
52 // This stack buffer has to live until the sendmsg() call!
53 union {
54 /* ancillary data buffer, wrapped in a union in order to ensure
55 it is suitably aligned */
56 char buf[CMSG_SPACE(SIZEOF_FDS)];
57 struct cmsghdr align;
58 } u;
59
60 // Optionally set up file descriptor data
61 if (fds[0] != -1) {
62 // If one FD is passed, all should be passed
63 assert(fds[1] != -1);
64 assert(fds[2] != -1);
65
66 msg.msg_control = u.buf;
67 msg.msg_controllen = sizeof u.buf;
68
69 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
70 cmsg->cmsg_level = SOL_SOCKET;
71 cmsg->cmsg_type = SCM_RIGHTS;
72 cmsg->cmsg_len = CMSG_LEN(SIZEOF_FDS);
73
74 int* fd_msg = (int*)CMSG_DATA(cmsg);
75 memcpy(fd_msg, fds, SIZEOF_FDS);
76 }
77
78 int num_bytes = sendmsg(sock_fd, &msg, 0);
79 if (num_bytes < 0) {
80 err->err_code = errno;
81 return;
82 }
83
84 buf[0] = ',';
85 if (write(sock_fd, buf, 1) < 0) {
86 err->err_code = errno;
87 return;
88 }
89}
90
91static int recv_fds_once(int sock_fd, int num_bytes, char* buf, int* buf_len,
92 int* fd_out, struct FanosError* err) {
93 // Where to put data
94 struct iovec iov = {0};
95 iov.iov_base = buf;
96 iov.iov_len = num_bytes; // number of bytes REQUESTED
97
98 struct msghdr msg = {0};
99 msg.msg_iov = &iov;
100 msg.msg_iovlen = 1;
101
102 union {
103 char control[CMSG_SPACE(SIZEOF_FDS)];
104 struct cmsghdr align;
105 } u;
106 msg.msg_control = u.control;
107 msg.msg_controllen = sizeof u.control;
108
109 size_t bytes_read = recvmsg(sock_fd, &msg, 0);
110 if (bytes_read < 0) {
111 err->err_code = errno;
112 return -1;
113 }
114 *buf_len = bytes_read;
115
116 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
117 if (cmsg && cmsg->cmsg_len == CMSG_LEN(SIZEOF_FDS)) {
118 if (cmsg->cmsg_level != SOL_SOCKET) {
119 err->value_err = kErrSolSocket;
120 return -1;
121 }
122 if (cmsg->cmsg_type != SCM_RIGHTS) {
123 err->value_err = kErrScmRights;
124 return -1;
125 }
126
127 int* fd_list = (int*)CMSG_DATA(cmsg);
128
129 // Append the descriptors received
130 fd_out[0] = fd_list[0];
131 fd_out[1] = fd_list[1];
132 fd_out[2] = fd_list[2];
133 }
134
135 return 0;
136}
137
138void fanos_recv(int sock_fd, int* fd_out, struct FanosResult* result_out,
139 struct FanosError* err) {
140 // Receive with netstring encoding
141 char buf[10]; // up to 9 digits, then :
142 char* p = buf;
143 int n;
144 for (int i = 0; i < 10; ++i) {
145 n = read(sock_fd, p, 1);
146 if (n < 0) {
147 err->err_code = errno;
148 return;
149 }
150 if (n != 1) {
151 if (i == 0) {
152 // read() returned 0 bytes, which means we got EOF at a message
153 // boundary. This is part of the protocol and the caller should handle
154 // it.
155 result_out->len = FANOS_EOF;
156 return;
157 } else {
158 err->value_err = kErrUnexpectedEof;
159 return;
160 }
161 }
162
163 if ('0' <= *p && *p <= '9') {
164 ; // added to the buffer
165 } else {
166 break;
167 }
168
169 p++;
170 }
171 if (p == buf) {
172 err->value_err = kErrMissingLength;
173 return;
174 }
175 if (*p != ':') {
176 err->value_err = kErrMissingColon;
177 return;
178 }
179
180 *p = '\0'; // change : to NUL terminator
181 int expected_bytes = atoi(buf);
182
183 char* data_buf = (char*)malloc(expected_bytes + 1);
184 data_buf[expected_bytes] = '\0';
185
186 n = 0;
187 while (n < expected_bytes) {
188 int bytes_read;
189 if (recv_fds_once(sock_fd, expected_bytes - n, data_buf + n, &bytes_read,
190 fd_out, err) < 0) {
191 return;
192 }
193 n += bytes_read;
194 break;
195 }
196
197 assert(n == expected_bytes);
198
199 n = read(sock_fd, buf, 1);
200 if (n < 0) {
201 err->err_code = errno;
202 return;
203 }
204 if (n != 1) {
205 err->value_err = kErrUnexpectedEof;
206 return;
207 }
208 if (buf[0] != ',') {
209 err->value_err = kErrMissingComma;
210 return;
211 }
212
213 result_out->data = data_buf;
214 result_out->len = expected_bytes;
215}