@logicseek/hello-cc 1.1.0-rc.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/docs/README.md +2 -0
- package/docs/README.zh-CN.md +2 -0
- package/docs/commands.md +16 -0
- package/docs/commands.zh-CN.md +14 -0
- package/docs/dsh.md +6 -0
- package/docs/dsh.zh-CN.md +5 -0
- package/docs/native.md +21 -1
- package/docs/native.zh-CN.md +19 -1
- package/docs/private-state.md +115 -0
- package/docs/private-state.zh-CN.md +92 -0
- package/lib/cli/app.mjs +46 -5
- package/lib/cli/commands/hook.mjs +31 -4
- package/lib/cli/commands/mcp.mjs +6 -0
- package/lib/cli/commands/native.mjs +17 -4
- package/lib/cli/commands/peer.mjs +3 -0
- package/lib/cli/commands/run.mjs +196 -62
- package/lib/cli/commands/tmux.mjs +28 -19
- package/lib/cli/commands/up.mjs +25 -17
- package/lib/cli-runtime.mjs +16 -1
- package/lib/core/coordination/event-retention.mjs +2 -0
- package/lib/core/sessions/codex-thread-root.mjs +144 -0
- package/lib/core/sessions/launch.mjs +4 -0
- package/lib/discover.mjs +95 -231
- package/lib/integrations/dsh-collaboration.mjs +9 -2
- package/lib/integrations/native/claude.mjs +6 -0
- package/lib/integrations/native/codex.mjs +4 -2
- package/lib/integrations/native/dsh-acp.mjs +1 -1
- package/lib/integrations/native/jsonrpc.mjs +2 -2
- package/lib/mcp/scope.mjs +50 -8
- package/lib/mcp/tools.mjs +15 -2
- package/lib/process/pinned-cwd.mjs +64 -0
- package/lib/process/pinned-guidance.mjs +1 -0
- package/lib/process/pinned-project-state.mjs +1 -0
- package/lib/process/tmux-cwd-handoff.mjs +277 -0
- package/lib/runtime/native/cli-submissions.mjs +220 -0
- package/lib/runtime/native/client.mjs +7 -1
- package/lib/runtime/native/launcher.mjs +20 -4
- package/lib/runtime/native/service.mjs +143 -50
- package/lib/runtime/native/store.mjs +303 -14
- package/lib/runtime/private-state.mjs +435 -24
- package/lib/runtime/project-path.mjs +53 -1
- package/lib/runtime/project-trust.mjs +6 -2
- package/lib/runtime/state-migration.mjs +66 -13
- package/lib/shared/redact.mjs +4 -1
- package/lib/terminal/tmux.mjs +13 -2
- package/lib/ui/help.mjs +1 -0
- package/lib/web/codex-app-server.mjs +3 -1
- package/lib/web/codex-sessions.mjs +68 -8
- package/lib/web/context-files.mjs +194 -41
- package/lib/web/http-routes.mjs +43 -12
- package/lib/web/native-sessions.mjs +3 -0
- package/lib/web/project-contexts.mjs +27 -5
- package/lib/web/project-files.mjs +108 -26
- package/lib/web/pty-sessions.mjs +15 -2
- package/lib/web/session-serialize.mjs +6 -1
- package/lib/web/startup.mjs +3 -1
- package/lib/web/tmux-sessions.mjs +153 -50
- package/lib/web/ui-interactions.mjs +7 -4
- package/native/bin/darwin-arm64/hcc-cwd-handoff +0 -0
- package/native/bin/darwin-x64/hcc-cwd-handoff +0 -0
- package/native/bin/linux-arm64/hcc-cwd-handoff +0 -0
- package/native/bin/linux-x64/hcc-cwd-handoff +0 -0
- package/native/cwd-handoff/README.md +45 -0
- package/native/cwd-handoff/hcc-cwd-handoff.c +693 -0
- package/package.json +8 -2
- package/scripts/build-cwd-handoff.mjs +47 -0
- package/scripts/pack-cwd-handoff.mjs +27 -0
- package/scripts/regression.mjs +113 -7
- package/scripts/verify-cwd-handoff-package.mjs +60 -0
- package/scripts/web-workbench-acceptance.mjs +79 -1
- package/scripts/web-workbench-installed-acceptance.mjs +11 -1
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* One-shot, authenticated directory-descriptor handoff for tmux panes.
|
|
3
|
+
*
|
|
4
|
+
* Build this helper for each supported OS/architecture before packaging. It
|
|
5
|
+
* is never compiled on an end-user's machine. The broker receives an already
|
|
6
|
+
* validated directory as fd 3 from its Node parent. A tmux pane starts the
|
|
7
|
+
* client without a user-selected pathname; only after the parent authorizes
|
|
8
|
+
* the exact pane PID does the broker transfer that fd with SCM_RIGHTS.
|
|
9
|
+
*/
|
|
10
|
+
#define _GNU_SOURCE
|
|
11
|
+
#define _DARWIN_C_SOURCE
|
|
12
|
+
|
|
13
|
+
#include <errno.h>
|
|
14
|
+
#include <fcntl.h>
|
|
15
|
+
#include <inttypes.h>
|
|
16
|
+
#include <limits.h>
|
|
17
|
+
#include <poll.h>
|
|
18
|
+
#include <signal.h>
|
|
19
|
+
#include <stdbool.h>
|
|
20
|
+
#include <stddef.h>
|
|
21
|
+
#include <stdint.h>
|
|
22
|
+
#include <stdio.h>
|
|
23
|
+
#include <stdlib.h>
|
|
24
|
+
#include <string.h>
|
|
25
|
+
#include <sys/socket.h>
|
|
26
|
+
#include <sys/stat.h>
|
|
27
|
+
#include <sys/types.h>
|
|
28
|
+
#include <sys/un.h>
|
|
29
|
+
#include <time.h>
|
|
30
|
+
#include <unistd.h>
|
|
31
|
+
|
|
32
|
+
#if !defined(__APPLE__) && !defined(__linux__)
|
|
33
|
+
#error "hcc-cwd-handoff supports macOS and Linux only"
|
|
34
|
+
#endif
|
|
35
|
+
|
|
36
|
+
#define HANDOFF_TIMEOUT_MS 20000
|
|
37
|
+
#define AUTH_WINDOW_MS 5000
|
|
38
|
+
#define REQUEST_TIMEOUT_MS 2000
|
|
39
|
+
#define RETRY_INTERVAL_MS 50
|
|
40
|
+
#define MAX_CLIENTS 64
|
|
41
|
+
#define LINE_CAP 256
|
|
42
|
+
#define NONCE_HEX_LENGTH 64
|
|
43
|
+
|
|
44
|
+
#ifndef HCC_SOURCE_SHA256
|
|
45
|
+
#error "Build with scripts/build-cwd-handoff.mjs to embed source provenance"
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
static const char build_id[] = "HCC_SOURCE_SHA256:" HCC_SOURCE_SHA256;
|
|
49
|
+
static struct sigaction previous_sigpipe;
|
|
50
|
+
|
|
51
|
+
typedef struct {
|
|
52
|
+
const char *socket_path;
|
|
53
|
+
const char *nonce;
|
|
54
|
+
dev_t dev;
|
|
55
|
+
ino_t ino;
|
|
56
|
+
int dir_fd;
|
|
57
|
+
char **exec_argv;
|
|
58
|
+
} options_t;
|
|
59
|
+
|
|
60
|
+
static bool socket_bound;
|
|
61
|
+
static char socket_basename[sizeof(((struct sockaddr_un *)0)->sun_path)];
|
|
62
|
+
static dev_t socket_dev;
|
|
63
|
+
static ino_t socket_ino;
|
|
64
|
+
|
|
65
|
+
static void diagnostic(const char *reason) {
|
|
66
|
+
fprintf(stderr, "HCC_CWD_HANDOFF:%s\n", reason);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static int error_exit(const char *reason) {
|
|
70
|
+
diagnostic(reason);
|
|
71
|
+
return 42;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static int64_t monotonic_ms(void) {
|
|
75
|
+
struct timespec ts;
|
|
76
|
+
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return -1;
|
|
77
|
+
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static int64_t deadline_after(int duration_ms) {
|
|
81
|
+
int64_t now = monotonic_ms();
|
|
82
|
+
return now < 0 ? -1 : now + duration_ms;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static int remaining_ms(int64_t deadline) {
|
|
86
|
+
int64_t now = monotonic_ms();
|
|
87
|
+
if (now < 0 || now >= deadline) return 0;
|
|
88
|
+
int64_t remaining = deadline - now;
|
|
89
|
+
return remaining > INT_MAX ? INT_MAX : (int)remaining;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static bool wait_fd(int fd, short events, int64_t deadline) {
|
|
93
|
+
struct pollfd pfd = { .fd = fd, .events = events, .revents = 0 };
|
|
94
|
+
while (remaining_ms(deadline) > 0) {
|
|
95
|
+
int rc = poll(&pfd, 1, remaining_ms(deadline));
|
|
96
|
+
if (rc > 0) return (pfd.revents & (events | POLLHUP | POLLERR)) != 0;
|
|
97
|
+
if (rc == 0) return false;
|
|
98
|
+
if (errno != EINTR) return false;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static bool write_all(int fd, const char *data, size_t size, int64_t deadline) {
|
|
104
|
+
size_t written = 0;
|
|
105
|
+
while (written < size) {
|
|
106
|
+
if (!wait_fd(fd, POLLOUT, deadline)) return false;
|
|
107
|
+
ssize_t count = write(fd, data + written, size - written);
|
|
108
|
+
if (count > 0) { written += (size_t)count; continue; }
|
|
109
|
+
if (count < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static bool read_line(int fd, char *buffer, size_t capacity, int64_t deadline) {
|
|
116
|
+
size_t length = 0;
|
|
117
|
+
while (length + 1 < capacity) {
|
|
118
|
+
if (!wait_fd(fd, POLLIN, deadline)) return false;
|
|
119
|
+
char byte;
|
|
120
|
+
ssize_t count = read(fd, &byte, 1);
|
|
121
|
+
if (count == 0) return false;
|
|
122
|
+
if (count < 0) {
|
|
123
|
+
if (errno == EINTR || errno == EAGAIN) continue;
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (byte == '\n') { buffer[length] = '\0'; return true; }
|
|
127
|
+
if ((unsigned char)byte < 0x20 || (unsigned char)byte > 0x7e) return false;
|
|
128
|
+
buffer[length++] = byte;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static bool set_cloexec(int fd) {
|
|
134
|
+
int flags = fcntl(fd, F_GETFD);
|
|
135
|
+
return flags >= 0 && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static bool set_nonblock(int fd) {
|
|
139
|
+
int flags = fcntl(fd, F_GETFL);
|
|
140
|
+
return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static bool parse_uint(const char *value, uintmax_t limit, uintmax_t *result) {
|
|
144
|
+
if (!value || !*value) return false;
|
|
145
|
+
uintmax_t parsed = 0;
|
|
146
|
+
for (const unsigned char *p = (const unsigned char *)value; *p; p++) {
|
|
147
|
+
if (*p < '0' || *p > '9') return false;
|
|
148
|
+
unsigned digit = (unsigned)(*p - '0');
|
|
149
|
+
if (parsed > (limit - digit) / 10) return false;
|
|
150
|
+
parsed = parsed * 10 + digit;
|
|
151
|
+
}
|
|
152
|
+
*result = parsed;
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static bool parse_nonce(const char *value) {
|
|
157
|
+
if (!value || strlen(value) != NONCE_HEX_LENGTH) return false;
|
|
158
|
+
for (const unsigned char *p = (const unsigned char *)value; *p; p++) {
|
|
159
|
+
if (!((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))) return false;
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Some platforms expose dev_t as signed and Node may render the same raw
|
|
165
|
+
* device ID as either a negative signed value or a positive bit pattern. */
|
|
166
|
+
static bool parse_device(const char *value, dev_t *result) {
|
|
167
|
+
if (!value || !*value) return false;
|
|
168
|
+
if (value[0] == '-') {
|
|
169
|
+
if (!value[1]) return false;
|
|
170
|
+
for (const unsigned char *p = (const unsigned char *)value + 1; *p; p++) {
|
|
171
|
+
if (*p < '0' || *p > '9') return false;
|
|
172
|
+
}
|
|
173
|
+
char *end;
|
|
174
|
+
errno = 0;
|
|
175
|
+
intmax_t parsed = strtoimax(value, &end, 10);
|
|
176
|
+
if (errno != 0 || *end != '\0') return false;
|
|
177
|
+
*result = (dev_t)parsed;
|
|
178
|
+
return (intmax_t)*result == parsed;
|
|
179
|
+
}
|
|
180
|
+
uintmax_t parsed;
|
|
181
|
+
if (!parse_uint(value, UINTMAX_MAX, &parsed)) return false;
|
|
182
|
+
uintmax_t mask = UINTMAX_MAX;
|
|
183
|
+
if (sizeof(dev_t) < sizeof(uintmax_t)) {
|
|
184
|
+
mask >>= (sizeof(uintmax_t) - sizeof(dev_t)) * CHAR_BIT;
|
|
185
|
+
}
|
|
186
|
+
if (parsed > mask) return false;
|
|
187
|
+
*result = (dev_t)parsed;
|
|
188
|
+
return ((uintmax_t)*result & mask) == parsed;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static bool parse_options(int argc, char **argv, bool broker, options_t *out) {
|
|
192
|
+
memset(out, 0, sizeof(*out));
|
|
193
|
+
out->dir_fd = -1;
|
|
194
|
+
bool seen_socket = false, seen_dev = false, seen_ino = false;
|
|
195
|
+
bool seen_fd = false, seen_nonce = false;
|
|
196
|
+
int i = 2;
|
|
197
|
+
for (; i < argc; i++) {
|
|
198
|
+
if (strcmp(argv[i], "--") == 0) { i++; break; }
|
|
199
|
+
if (i + 1 >= argc) return false;
|
|
200
|
+
const char *flag = argv[i++], *value = argv[i];
|
|
201
|
+
uintmax_t parsed;
|
|
202
|
+
if (strcmp(flag, "--socket") == 0 && !seen_socket) {
|
|
203
|
+
out->socket_path = value; seen_socket = true;
|
|
204
|
+
} else if (strcmp(flag, "--dev") == 0 && !seen_dev &&
|
|
205
|
+
parse_device(value, &out->dev)) {
|
|
206
|
+
seen_dev = true;
|
|
207
|
+
} else if (strcmp(flag, "--ino") == 0 && !seen_ino &&
|
|
208
|
+
parse_uint(value, UINTMAX_MAX, &parsed)) {
|
|
209
|
+
out->ino = (ino_t)parsed;
|
|
210
|
+
if ((uintmax_t)out->ino != parsed) return false;
|
|
211
|
+
seen_ino = true;
|
|
212
|
+
} else if (strcmp(flag, "--dir-fd") == 0 && broker && !seen_fd &&
|
|
213
|
+
parse_uint(value, INT_MAX, &parsed)) {
|
|
214
|
+
out->dir_fd = (int)parsed; seen_fd = true;
|
|
215
|
+
} else if (strcmp(flag, "--nonce") == 0 && !seen_nonce && parse_nonce(value)) {
|
|
216
|
+
out->nonce = value; seen_nonce = true;
|
|
217
|
+
} else return false;
|
|
218
|
+
}
|
|
219
|
+
if (!seen_socket || !seen_dev || !seen_ino || !seen_nonce ||
|
|
220
|
+
(broker && (!seen_fd || i != argc)) || (!broker && (seen_fd || i >= argc))) return false;
|
|
221
|
+
if (!out->socket_path || out->socket_path[0] != '/') return false;
|
|
222
|
+
if (!broker) {
|
|
223
|
+
if (argv[i][0] == '\0') return false;
|
|
224
|
+
out->exec_argv = argv + i;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static bool same_directory(const struct stat *st, const options_t *options) {
|
|
230
|
+
return S_ISDIR(st->st_mode) && st->st_dev == options->dev && st->st_ino == options->ino;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* The AF_UNIX name lives only in a private, user-owned, non-symlink directory.
|
|
234
|
+
* The broker pins that directory with fchdir before binding the basename. */
|
|
235
|
+
static bool socket_parent(const char *socket_path, char *basename, size_t basename_cap,
|
|
236
|
+
int *parent_fd) {
|
|
237
|
+
size_t length = strlen(socket_path);
|
|
238
|
+
if (length == 0 || length >= sizeof(((struct sockaddr_un *)0)->sun_path)) return false;
|
|
239
|
+
const char *slash = strrchr(socket_path, '/');
|
|
240
|
+
if (!slash || !slash[1] || strcmp(slash + 1, ".") == 0 ||
|
|
241
|
+
strcmp(slash + 1, "..") == 0 || strlen(slash + 1) >= basename_cap) return false;
|
|
242
|
+
size_t parent_length = (size_t)(slash - socket_path);
|
|
243
|
+
char *parent = malloc(parent_length + 2);
|
|
244
|
+
if (!parent) return false;
|
|
245
|
+
if (parent_length == 0) { parent[0] = '/'; parent[1] = '\0'; }
|
|
246
|
+
else { memcpy(parent, socket_path, parent_length); parent[parent_length] = '\0'; }
|
|
247
|
+
int fd = open(parent, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
|
248
|
+
free(parent);
|
|
249
|
+
if (fd < 0) return false;
|
|
250
|
+
struct stat st;
|
|
251
|
+
if (fstat(fd, &st) != 0 || !S_ISDIR(st.st_mode) || st.st_uid != geteuid() ||
|
|
252
|
+
(st.st_mode & 0077) != 0) { close(fd); return false; }
|
|
253
|
+
strcpy(basename, slash + 1);
|
|
254
|
+
*parent_fd = fd;
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
static bool socket_address(const char *path, struct sockaddr_un *address,
|
|
259
|
+
socklen_t *address_len) {
|
|
260
|
+
size_t length = strlen(path);
|
|
261
|
+
if (length == 0 || length >= sizeof(address->sun_path)) return false;
|
|
262
|
+
memset(address, 0, sizeof(*address));
|
|
263
|
+
address->sun_family = AF_UNIX;
|
|
264
|
+
memcpy(address->sun_path, path, length + 1);
|
|
265
|
+
*address_len = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + length + 1);
|
|
266
|
+
#ifdef __APPLE__
|
|
267
|
+
address->sun_len = (uint8_t)*address_len;
|
|
268
|
+
#endif
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static void cleanup_socket(void) {
|
|
273
|
+
if (!socket_bound) return;
|
|
274
|
+
struct stat st;
|
|
275
|
+
if (lstat(socket_basename, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
|
276
|
+
st.st_dev == socket_dev && st.st_ino == socket_ino) {
|
|
277
|
+
unlink(socket_basename);
|
|
278
|
+
}
|
|
279
|
+
socket_bound = false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static bool receipt_names(const char *basename, char *final, size_t final_cap,
|
|
283
|
+
char *temporary, size_t temporary_cap) {
|
|
284
|
+
int final_length = snprintf(final, final_cap, "%s.bound", basename);
|
|
285
|
+
int temporary_length = snprintf(temporary, temporary_cap, "%s.bound.tmp", basename);
|
|
286
|
+
return final_length > 0 && (size_t)final_length < final_cap &&
|
|
287
|
+
temporary_length > 0 && (size_t)temporary_length < temporary_cap;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
static bool name_absent(const char *name) {
|
|
291
|
+
struct stat st;
|
|
292
|
+
return lstat(name, &st) != 0 && errno == ENOENT;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* Publish only after an authenticated client has acknowledged fchdir. The
|
|
296
|
+
* final pathname is never visible with partial content. It is left for the
|
|
297
|
+
* parent to validate and remove after synchronous launch admission. */
|
|
298
|
+
static bool publish_bound_receipt(int parent_fd, const char *basename,
|
|
299
|
+
const char *nonce) {
|
|
300
|
+
char final[sizeof(((struct sockaddr_un *)0)->sun_path) + 16];
|
|
301
|
+
char temporary[sizeof(((struct sockaddr_un *)0)->sun_path) + 16];
|
|
302
|
+
if (!receipt_names(basename, final, sizeof(final), temporary,
|
|
303
|
+
sizeof(temporary)) || !name_absent(final)) return false;
|
|
304
|
+
int fd = open(temporary, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
|
|
305
|
+
if (fd < 0) return false;
|
|
306
|
+
struct stat created = { 0 }, current;
|
|
307
|
+
bool created_valid = fstat(fd, &created) == 0;
|
|
308
|
+
bool renamed = false, success = false;
|
|
309
|
+
if (!created_valid || !S_ISREG(created.st_mode) ||
|
|
310
|
+
created.st_uid != geteuid() || created.st_nlink != 1 ||
|
|
311
|
+
(created.st_mode & 0777) != 0600) goto done;
|
|
312
|
+
char payload[NONCE_HEX_LENGTH + 2];
|
|
313
|
+
memcpy(payload, nonce, NONCE_HEX_LENGTH);
|
|
314
|
+
payload[NONCE_HEX_LENGTH] = '\n';
|
|
315
|
+
size_t written = 0;
|
|
316
|
+
while (written < NONCE_HEX_LENGTH + 1) {
|
|
317
|
+
ssize_t count = write(fd, payload + written, NONCE_HEX_LENGTH + 1 - written);
|
|
318
|
+
if (count > 0) { written += (size_t)count; continue; }
|
|
319
|
+
if (count < 0 && errno == EINTR) continue;
|
|
320
|
+
goto done;
|
|
321
|
+
}
|
|
322
|
+
if (fsync(fd) != 0) goto done;
|
|
323
|
+
if (close(fd) != 0) { fd = -1; goto done; }
|
|
324
|
+
fd = -1;
|
|
325
|
+
if (!name_absent(final) || rename(temporary, final) != 0) goto done;
|
|
326
|
+
renamed = true;
|
|
327
|
+
if (lstat(final, ¤t) != 0 || !S_ISREG(current.st_mode) ||
|
|
328
|
+
current.st_dev != created.st_dev || current.st_ino != created.st_ino ||
|
|
329
|
+
current.st_uid != geteuid() || current.st_nlink != 1 ||
|
|
330
|
+
(current.st_mode & 0777) != 0600 || fsync(parent_fd) != 0) goto done;
|
|
331
|
+
success = true;
|
|
332
|
+
|
|
333
|
+
done:
|
|
334
|
+
if (fd >= 0) close(fd);
|
|
335
|
+
const char *cleanup = renamed ? final : temporary;
|
|
336
|
+
if (!success && created_valid && lstat(cleanup, ¤t) == 0 &&
|
|
337
|
+
current.st_dev == created.st_dev && current.st_ino == created.st_ino) {
|
|
338
|
+
unlink(cleanup);
|
|
339
|
+
}
|
|
340
|
+
return success;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static bool peer_identity(int fd, pid_t *pid, uid_t *uid) {
|
|
344
|
+
#ifdef __linux__
|
|
345
|
+
struct ucred credentials;
|
|
346
|
+
socklen_t size = sizeof(credentials);
|
|
347
|
+
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &size) != 0 ||
|
|
348
|
+
size != sizeof(credentials)) return false;
|
|
349
|
+
*pid = credentials.pid;
|
|
350
|
+
*uid = credentials.uid;
|
|
351
|
+
#else
|
|
352
|
+
socklen_t size = sizeof(*pid);
|
|
353
|
+
if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, pid, &size) != 0 ||
|
|
354
|
+
size != sizeof(*pid)) return false;
|
|
355
|
+
gid_t group;
|
|
356
|
+
if (getpeereid(fd, uid, &group) != 0) return false;
|
|
357
|
+
#endif
|
|
358
|
+
return *pid > 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
static bool send_directory_fd(int socket_fd, int dir_fd, int64_t deadline) {
|
|
362
|
+
char marker = 'F';
|
|
363
|
+
struct iovec iov = { .iov_base = &marker, .iov_len = 1 };
|
|
364
|
+
union { struct cmsghdr align; char bytes[CMSG_SPACE(sizeof(int))]; } ancillary;
|
|
365
|
+
memset(&ancillary, 0, sizeof(ancillary));
|
|
366
|
+
struct msghdr message = { 0 };
|
|
367
|
+
message.msg_iov = &iov;
|
|
368
|
+
message.msg_iovlen = 1;
|
|
369
|
+
message.msg_control = ancillary.bytes;
|
|
370
|
+
message.msg_controllen = sizeof(ancillary.bytes);
|
|
371
|
+
struct cmsghdr *control = CMSG_FIRSTHDR(&message);
|
|
372
|
+
if (!control) return false;
|
|
373
|
+
control->cmsg_level = SOL_SOCKET;
|
|
374
|
+
control->cmsg_type = SCM_RIGHTS;
|
|
375
|
+
control->cmsg_len = CMSG_LEN(sizeof(int));
|
|
376
|
+
memcpy(CMSG_DATA(control), &dir_fd, sizeof(dir_fd));
|
|
377
|
+
while (wait_fd(socket_fd, POLLOUT, deadline)) {
|
|
378
|
+
ssize_t count = sendmsg(socket_fd, &message, 0);
|
|
379
|
+
if (count == 1) return true;
|
|
380
|
+
if (count < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
static int receive_directory_fd(int socket_fd, int64_t deadline) {
|
|
387
|
+
char marker = '\0';
|
|
388
|
+
struct iovec iov = { .iov_base = &marker, .iov_len = 1 };
|
|
389
|
+
union { struct cmsghdr align; char bytes[CMSG_SPACE(sizeof(int) * 8)]; } ancillary;
|
|
390
|
+
memset(&ancillary, 0, sizeof(ancillary));
|
|
391
|
+
struct msghdr message = { 0 };
|
|
392
|
+
message.msg_iov = &iov;
|
|
393
|
+
message.msg_iovlen = 1;
|
|
394
|
+
message.msg_control = ancillary.bytes;
|
|
395
|
+
while (wait_fd(socket_fd, POLLIN, deadline)) {
|
|
396
|
+
message.msg_controllen = sizeof(ancillary.bytes);
|
|
397
|
+
message.msg_flags = 0;
|
|
398
|
+
ssize_t count = recvmsg(socket_fd, &message, 0);
|
|
399
|
+
if (count < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
|
400
|
+
if (count < 0) return -1;
|
|
401
|
+
int received = -1, total = 0;
|
|
402
|
+
bool unexpected_control = false;
|
|
403
|
+
for (struct cmsghdr *control = CMSG_FIRSTHDR(&message); control;
|
|
404
|
+
control = CMSG_NXTHDR(&message, control)) {
|
|
405
|
+
if (control->cmsg_level != SOL_SOCKET || control->cmsg_type != SCM_RIGHTS ||
|
|
406
|
+
control->cmsg_len < CMSG_LEN(0)) {
|
|
407
|
+
unexpected_control = true;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
size_t bytes = control->cmsg_len - CMSG_LEN(0);
|
|
411
|
+
if (bytes % sizeof(int) != 0) unexpected_control = true;
|
|
412
|
+
size_t count_fds = bytes / sizeof(int);
|
|
413
|
+
int *fds = (int *)CMSG_DATA(control);
|
|
414
|
+
for (size_t i = 0; i < count_fds; i++) {
|
|
415
|
+
if (total++ == 0) received = fds[i];
|
|
416
|
+
else close(fds[i]);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (count != 1 || marker != 'F' ||
|
|
420
|
+
(message.msg_flags & (MSG_CTRUNC | MSG_TRUNC)) ||
|
|
421
|
+
unexpected_control || total != 1) {
|
|
422
|
+
if (received >= 0) close(received);
|
|
423
|
+
return -1;
|
|
424
|
+
}
|
|
425
|
+
if (!set_cloexec(received)) { close(received); return -1; }
|
|
426
|
+
return received;
|
|
427
|
+
}
|
|
428
|
+
return -1;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
static bool parse_allow(const char *line, const char *nonce, pid_t *pid) {
|
|
432
|
+
if (strncmp(line, "ALLOW ", 6) != 0) return false;
|
|
433
|
+
const char *space = strchr(line + 6, ' ');
|
|
434
|
+
if (!space || strcmp(space + 1, nonce) != 0) return false;
|
|
435
|
+
size_t digits = (size_t)(space - (line + 6));
|
|
436
|
+
if (digits == 0 || digits >= 24) return false;
|
|
437
|
+
char number[24];
|
|
438
|
+
memcpy(number, line + 6, digits);
|
|
439
|
+
number[digits] = '\0';
|
|
440
|
+
uintmax_t parsed;
|
|
441
|
+
if (!parse_uint(number, INT_MAX, &parsed) || parsed == 0) return false;
|
|
442
|
+
*pid = (pid_t)parsed;
|
|
443
|
+
return (uintmax_t)*pid == parsed;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
static int broker(const options_t *options) {
|
|
447
|
+
struct stat dir_stat;
|
|
448
|
+
if (options->dir_fd != 3 || fstat(options->dir_fd, &dir_stat) != 0 ||
|
|
449
|
+
!same_directory(&dir_stat, options) || !set_cloexec(options->dir_fd)) {
|
|
450
|
+
return error_exit("BROKER_DIRECTORY_INVALID");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
int parent_fd = -1, listener = -1, rc = 42;
|
|
454
|
+
if (!socket_parent(options->socket_path, socket_basename,
|
|
455
|
+
sizeof(socket_basename), &parent_fd)) {
|
|
456
|
+
return error_exit("SOCKET_PARENT_INVALID");
|
|
457
|
+
}
|
|
458
|
+
if (fchdir(parent_fd) != 0) { close(parent_fd); return error_exit("SOCKET_PARENT_CHANGED"); }
|
|
459
|
+
struct stat current_parent, original_parent;
|
|
460
|
+
if (fstat(parent_fd, &original_parent) != 0 || stat(".", ¤t_parent) != 0 ||
|
|
461
|
+
original_parent.st_dev != current_parent.st_dev ||
|
|
462
|
+
original_parent.st_ino != current_parent.st_ino) {
|
|
463
|
+
close(parent_fd);
|
|
464
|
+
return error_exit("SOCKET_PARENT_CHANGED");
|
|
465
|
+
}
|
|
466
|
+
struct stat existing;
|
|
467
|
+
char receipt_final[sizeof(((struct sockaddr_un *)0)->sun_path) + 16];
|
|
468
|
+
char receipt_temporary[sizeof(((struct sockaddr_un *)0)->sun_path) + 16];
|
|
469
|
+
if (!receipt_names(socket_basename, receipt_final, sizeof(receipt_final),
|
|
470
|
+
receipt_temporary, sizeof(receipt_temporary)) ||
|
|
471
|
+
!name_absent(socket_basename) || !name_absent(receipt_final) ||
|
|
472
|
+
!name_absent(receipt_temporary)) {
|
|
473
|
+
rc = error_exit("SOCKET_EXISTS"); goto done;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
struct sockaddr_un address;
|
|
477
|
+
socklen_t address_len;
|
|
478
|
+
if (!socket_address(socket_basename, &address, &address_len)) {
|
|
479
|
+
rc = error_exit("SOCKET_PATH_INVALID"); goto done;
|
|
480
|
+
}
|
|
481
|
+
listener = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
482
|
+
if (listener < 0 || !set_cloexec(listener) || !set_nonblock(listener)) {
|
|
483
|
+
rc = error_exit("BROKER_SOCKET_FAILED"); goto done;
|
|
484
|
+
}
|
|
485
|
+
umask(0077);
|
|
486
|
+
if (bind(listener, (struct sockaddr *)&address, address_len) != 0) {
|
|
487
|
+
rc = error_exit("BROKER_BIND_FAILED"); goto done;
|
|
488
|
+
}
|
|
489
|
+
if (lstat(socket_basename, &existing) != 0 || !S_ISSOCK(existing.st_mode) ||
|
|
490
|
+
existing.st_uid != geteuid()) {
|
|
491
|
+
rc = error_exit("BROKER_BIND_FAILED"); goto done;
|
|
492
|
+
}
|
|
493
|
+
socket_dev = existing.st_dev;
|
|
494
|
+
socket_ino = existing.st_ino;
|
|
495
|
+
socket_bound = true;
|
|
496
|
+
if (chmod(socket_basename, 0600) != 0 ||
|
|
497
|
+
lstat(socket_basename, &existing) != 0 || !S_ISSOCK(existing.st_mode) ||
|
|
498
|
+
existing.st_dev != socket_dev || existing.st_ino != socket_ino ||
|
|
499
|
+
existing.st_uid != geteuid() || (existing.st_mode & 0077) != 0) {
|
|
500
|
+
rc = error_exit("BROKER_BIND_FAILED"); goto done;
|
|
501
|
+
}
|
|
502
|
+
if (listen(listener, 4) != 0) { rc = error_exit("BROKER_LISTEN_FAILED"); goto done; }
|
|
503
|
+
if (!write_all(STDOUT_FILENO, "READY\n", 6, deadline_after(REQUEST_TIMEOUT_MS))) {
|
|
504
|
+
rc = error_exit("BROKER_READY_FAILED"); goto done;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
char line[LINE_CAP];
|
|
508
|
+
pid_t expected_pid;
|
|
509
|
+
if (!read_line(STDIN_FILENO, line, sizeof(line), deadline_after(HANDOFF_TIMEOUT_MS)) ||
|
|
510
|
+
!parse_allow(line, options->nonce, &expected_pid)) {
|
|
511
|
+
rc = error_exit("BROKER_ALLOW_INVALID"); goto done;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
int64_t deadline = deadline_after(AUTH_WINDOW_MS);
|
|
515
|
+
int attempts = 0;
|
|
516
|
+
while (remaining_ms(deadline) > 0 && attempts++ < MAX_CLIENTS) {
|
|
517
|
+
if (!wait_fd(listener, POLLIN, deadline)) break;
|
|
518
|
+
int peer = accept(listener, NULL, NULL);
|
|
519
|
+
if (peer < 0) { if (errno == EINTR || errno == EAGAIN) continue; break; }
|
|
520
|
+
if (!set_cloexec(peer) || !set_nonblock(peer)) { close(peer); continue; }
|
|
521
|
+
pid_t actual_pid;
|
|
522
|
+
uid_t actual_uid;
|
|
523
|
+
if (!peer_identity(peer, &actual_pid, &actual_uid) ||
|
|
524
|
+
actual_pid != expected_pid || actual_uid != geteuid()) {
|
|
525
|
+
close(peer);
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
int64_t request_deadline = deadline_after(REQUEST_TIMEOUT_MS);
|
|
529
|
+
if (!read_line(peer, line, sizeof(line), request_deadline)) {
|
|
530
|
+
close(peer); rc = error_exit("CLIENT_HELLO_FAILED"); goto done;
|
|
531
|
+
}
|
|
532
|
+
char expected_hello[LINE_CAP];
|
|
533
|
+
snprintf(expected_hello, sizeof(expected_hello), "HELLO %s", options->nonce);
|
|
534
|
+
if (strcmp(line, expected_hello) != 0) {
|
|
535
|
+
close(peer); rc = error_exit("CLIENT_NONCE_INVALID"); goto done;
|
|
536
|
+
}
|
|
537
|
+
if (!send_directory_fd(peer, options->dir_fd, request_deadline)) {
|
|
538
|
+
close(peer); rc = error_exit("FD_TRANSFER_FAILED"); goto done;
|
|
539
|
+
}
|
|
540
|
+
if (!read_line(peer, line, sizeof(line), deadline)) {
|
|
541
|
+
close(peer); rc = error_exit("CLIENT_BIND_UNCONFIRMED"); goto done;
|
|
542
|
+
}
|
|
543
|
+
char expected_bound[LINE_CAP];
|
|
544
|
+
snprintf(expected_bound, sizeof(expected_bound), "BOUND %s", options->nonce);
|
|
545
|
+
bool bound = strcmp(line, expected_bound) == 0;
|
|
546
|
+
close(peer);
|
|
547
|
+
if (!bound) { rc = error_exit("CLIENT_BIND_UNCONFIRMED"); goto done; }
|
|
548
|
+
if (!publish_bound_receipt(parent_fd, socket_basename, options->nonce)) {
|
|
549
|
+
rc = error_exit("BROKER_BOUND_RECEIPT_FAILED"); goto done;
|
|
550
|
+
}
|
|
551
|
+
if (!write_all(STDOUT_FILENO, "BOUND\n", 6, deadline_after(REQUEST_TIMEOUT_MS))) {
|
|
552
|
+
rc = error_exit("BROKER_BOUND_FAILED"); goto done;
|
|
553
|
+
}
|
|
554
|
+
rc = 0;
|
|
555
|
+
goto done;
|
|
556
|
+
}
|
|
557
|
+
rc = error_exit("BROKER_CLIENT_TIMEOUT");
|
|
558
|
+
|
|
559
|
+
done:
|
|
560
|
+
if (listener >= 0) close(listener);
|
|
561
|
+
cleanup_socket();
|
|
562
|
+
if (parent_fd >= 0) close(parent_fd);
|
|
563
|
+
return rc;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
static int connect_client(const char *socket_path, int64_t deadline) {
|
|
567
|
+
struct sockaddr_un address;
|
|
568
|
+
socklen_t address_len;
|
|
569
|
+
if (strlen(socket_path) >= sizeof(address.sun_path)) return -1;
|
|
570
|
+
char basename[sizeof(address.sun_path)];
|
|
571
|
+
int parent_fd = -1;
|
|
572
|
+
if (!socket_parent(socket_path, basename, sizeof(basename), &parent_fd)) return -1;
|
|
573
|
+
struct stat parent, current;
|
|
574
|
+
if (fstat(parent_fd, &parent) != 0 || fchdir(parent_fd) != 0 ||
|
|
575
|
+
stat(".", ¤t) != 0 || parent.st_dev != current.st_dev ||
|
|
576
|
+
parent.st_ino != current.st_ino ||
|
|
577
|
+
!socket_address(basename, &address, &address_len)) {
|
|
578
|
+
close(parent_fd);
|
|
579
|
+
return -1;
|
|
580
|
+
}
|
|
581
|
+
while (remaining_ms(deadline) > 0) {
|
|
582
|
+
struct stat st;
|
|
583
|
+
if (fstatat(parent_fd, basename, &st, AT_SYMLINK_NOFOLLOW) != 0) {
|
|
584
|
+
if (errno != ENOENT) break;
|
|
585
|
+
} else if (!S_ISSOCK(st.st_mode) || st.st_uid != geteuid() ||
|
|
586
|
+
(st.st_mode & 0077) != 0) break;
|
|
587
|
+
else {
|
|
588
|
+
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
589
|
+
if (fd < 0) break;
|
|
590
|
+
if (!set_cloexec(fd) || !set_nonblock(fd)) { close(fd); break; }
|
|
591
|
+
if (connect(fd, (struct sockaddr *)&address, address_len) == 0) {
|
|
592
|
+
close(parent_fd); return fd;
|
|
593
|
+
}
|
|
594
|
+
int cause = errno;
|
|
595
|
+
if (cause == EINPROGRESS || cause == EAGAIN) {
|
|
596
|
+
if (wait_fd(fd, POLLOUT, deadline)) {
|
|
597
|
+
socklen_t size = sizeof(cause);
|
|
598
|
+
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &cause, &size) == 0 && cause == 0) {
|
|
599
|
+
close(parent_fd); return fd;
|
|
600
|
+
}
|
|
601
|
+
} else cause = ETIMEDOUT;
|
|
602
|
+
}
|
|
603
|
+
close(fd);
|
|
604
|
+
if (cause != ENOENT && cause != ECONNREFUSED && cause != EAGAIN) break;
|
|
605
|
+
}
|
|
606
|
+
struct timespec pause = { .tv_sec = 0, .tv_nsec = RETRY_INTERVAL_MS * 1000000L };
|
|
607
|
+
nanosleep(&pause, NULL);
|
|
608
|
+
}
|
|
609
|
+
close(parent_fd);
|
|
610
|
+
return -1;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
static char *current_cwd(void) {
|
|
614
|
+
size_t capacity = 256;
|
|
615
|
+
while (capacity <= 1024 * 1024) {
|
|
616
|
+
char *buffer = malloc(capacity);
|
|
617
|
+
if (!buffer) return NULL;
|
|
618
|
+
if (getcwd(buffer, capacity)) return buffer;
|
|
619
|
+
int cause = errno;
|
|
620
|
+
free(buffer);
|
|
621
|
+
if (cause != ERANGE) return NULL;
|
|
622
|
+
capacity *= 2;
|
|
623
|
+
}
|
|
624
|
+
return NULL;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
static int client(const options_t *options) {
|
|
628
|
+
int fd = connect_client(options->socket_path, deadline_after(HANDOFF_TIMEOUT_MS));
|
|
629
|
+
if (fd < 0) return error_exit("CLIENT_CONNECT_FAILED");
|
|
630
|
+
pid_t broker_pid;
|
|
631
|
+
uid_t broker_uid;
|
|
632
|
+
if (!peer_identity(fd, &broker_pid, &broker_uid) || broker_uid != geteuid()) {
|
|
633
|
+
close(fd); return error_exit("BROKER_IDENTITY_INVALID");
|
|
634
|
+
}
|
|
635
|
+
char hello[LINE_CAP];
|
|
636
|
+
int size = snprintf(hello, sizeof(hello), "HELLO %s\n", options->nonce);
|
|
637
|
+
if (size < 0 || (size_t)size >= sizeof(hello) ||
|
|
638
|
+
!write_all(fd, hello, (size_t)size, deadline_after(REQUEST_TIMEOUT_MS))) {
|
|
639
|
+
close(fd); return error_exit("CLIENT_HELLO_FAILED");
|
|
640
|
+
}
|
|
641
|
+
int directory_fd = receive_directory_fd(fd, deadline_after(HANDOFF_TIMEOUT_MS));
|
|
642
|
+
if (directory_fd < 0) { close(fd); return error_exit("FD_RECEIVE_FAILED"); }
|
|
643
|
+
struct stat received, current;
|
|
644
|
+
if (fstat(directory_fd, &received) != 0 || !same_directory(&received, options) ||
|
|
645
|
+
fchdir(directory_fd) != 0 || stat(".", ¤t) != 0 ||
|
|
646
|
+
!same_directory(¤t, options)) {
|
|
647
|
+
close(directory_fd); close(fd); return error_exit("CLIENT_DIRECTORY_CHANGED");
|
|
648
|
+
}
|
|
649
|
+
char *cwd = current_cwd();
|
|
650
|
+
if (!cwd || setenv("PWD", cwd, 1) != 0) {
|
|
651
|
+
free(cwd); close(directory_fd); close(fd);
|
|
652
|
+
return error_exit("CLIENT_CWD_UNAVAILABLE");
|
|
653
|
+
}
|
|
654
|
+
free(cwd);
|
|
655
|
+
char bound[LINE_CAP];
|
|
656
|
+
size = snprintf(bound, sizeof(bound), "BOUND %s\n", options->nonce);
|
|
657
|
+
if (size < 0 || (size_t)size >= sizeof(bound) ||
|
|
658
|
+
!write_all(fd, bound, (size_t)size, deadline_after(REQUEST_TIMEOUT_MS))) {
|
|
659
|
+
close(directory_fd); close(fd); return error_exit("CLIENT_BOUND_FAILED");
|
|
660
|
+
}
|
|
661
|
+
close(fd);
|
|
662
|
+
/* Retain directory_fd, marked CLOEXEC, until the successful exec. */
|
|
663
|
+
if (sigaction(SIGPIPE, &previous_sigpipe, NULL) != 0) {
|
|
664
|
+
close(directory_fd);
|
|
665
|
+
return error_exit("CLIENT_SIGNAL_RESTORE_FAILED");
|
|
666
|
+
}
|
|
667
|
+
execvp(options->exec_argv[0], options->exec_argv);
|
|
668
|
+
int cause = errno;
|
|
669
|
+
close(directory_fd);
|
|
670
|
+
fprintf(stderr, "HCC_CWD_HANDOFF:CLIENT_EXEC_FAILED:%d\n", cause);
|
|
671
|
+
return 127;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
int main(int argc, char **argv) {
|
|
675
|
+
if (argc == 2 && strcmp(argv[1], "--build-id") == 0) {
|
|
676
|
+
puts(build_id);
|
|
677
|
+
return 0;
|
|
678
|
+
}
|
|
679
|
+
if (argc < 3 || (strcmp(argv[1], "broker") != 0 && strcmp(argv[1], "client") != 0)) {
|
|
680
|
+
diagnostic("USAGE"); return 64;
|
|
681
|
+
}
|
|
682
|
+
bool is_broker = strcmp(argv[1], "broker") == 0;
|
|
683
|
+
options_t options;
|
|
684
|
+
if (!parse_options(argc, argv, is_broker, &options)) {
|
|
685
|
+
diagnostic("USAGE"); return 64;
|
|
686
|
+
}
|
|
687
|
+
struct sigaction ignore_sigpipe = { .sa_handler = SIG_IGN };
|
|
688
|
+
sigemptyset(&ignore_sigpipe.sa_mask);
|
|
689
|
+
if (sigaction(SIGPIPE, &ignore_sigpipe, &previous_sigpipe) != 0) {
|
|
690
|
+
return error_exit("SIGNAL_SETUP_FAILED");
|
|
691
|
+
}
|
|
692
|
+
return is_broker ? broker(&options) : client(&options);
|
|
693
|
+
}
|