]> git.proxmox.com Git - ceph.git/blob - ceph/src/pmdk/src/librpmem/rpmem_ssh.c
import ceph 16.2.7
[ceph.git] / ceph / src / pmdk / src / librpmem / rpmem_ssh.c
1 // SPDX-License-Identifier: BSD-3-Clause
2 /* Copyright 2016-2020, Intel Corporation */
3
4 /*
5 * rpmem_ssh.c -- rpmem ssh transport layer source file
6 */
7
8 #include <unistd.h>
9 #include <errno.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <stdint.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16
17 #include "util.h"
18 #include "os.h"
19 #include "out.h"
20 #include "rpmem_common.h"
21 #include "rpmem_ssh.h"
22 #include "rpmem_cmd.h"
23 #include "rpmem_util.h"
24
25 #define ERR_BUFF_LEN 4095
26
27 /* +1 in order to be sure it is always null-terminated */
28 static char error_str[ERR_BUFF_LEN + 1];
29
30 struct rpmem_ssh {
31 struct rpmem_cmd *cmd;
32 };
33
34 /*
35 * get_ssh -- return ssh command name
36 */
37 static const char *
38 get_ssh(void)
39 {
40 char *cmd = os_getenv(RPMEM_SSH_ENV);
41 if (!cmd)
42 cmd = RPMEM_DEF_SSH;
43
44 return cmd;
45 }
46
47 /*
48 * get_user_at_node -- returns string containing user@node
49 */
50 static char *
51 get_user_at_node(const struct rpmem_target_info *info)
52 {
53 char *user_at_node = NULL;
54
55 if (info->flags & RPMEM_HAS_USER) {
56 size_t ulen = strlen(info->user);
57 size_t nlen = strlen(info->node);
58 size_t len = ulen + 1 + nlen + 1;
59 user_at_node = malloc(len);
60 if (!user_at_node)
61 goto err_malloc;
62 int ret = util_snprintf(user_at_node, len, "%s@%s",
63 info->user, info->node);
64 if (ret < 0)
65 goto err_printf;
66 } else {
67 user_at_node = strdup(info->node);
68 if (!user_at_node)
69 goto err_malloc;
70 }
71
72 return user_at_node;
73 err_printf:
74 free(user_at_node);
75 err_malloc:
76 return NULL;
77 }
78
79 /*
80 * get_cmd -- return an RPMEM_CMD with appended list of arguments
81 */
82 static char *
83 get_cmd(const char **argv)
84 {
85 const char *env_cmd = rpmem_util_cmd_get();
86 char *cmd = strdup(env_cmd);
87 if (!cmd)
88 return NULL;
89
90 size_t cmd_len = strlen(cmd) + 1;
91
92 const char *arg;
93 while ((arg = *argv++) != NULL) {
94 size_t len = strlen(arg);
95 size_t new_cmd_len = cmd_len + len + 1;
96 char *tmp = realloc(cmd, new_cmd_len);
97 if (!tmp)
98 goto err;
99
100 cmd = tmp;
101
102 /* append the argument to the command */
103 cmd[cmd_len - 1] = ' ';
104 memcpy(&cmd[cmd_len], arg, len);
105 cmd[cmd_len + len] = '\0';
106
107 cmd_len = new_cmd_len;
108 }
109
110 return cmd;
111 err:
112 free(cmd);
113 return NULL;
114 }
115
116 /*
117 * valist_to_argv -- convert va_list to argv array
118 */
119 static const char **
120 valist_to_argv(va_list args)
121 {
122 const char **argv = malloc(sizeof(const char *));
123 if (!argv)
124 return NULL;
125
126 argv[0] = NULL;
127 size_t nargs = 0;
128
129 const char *arg;
130 while ((arg = va_arg(args, const char *)) != NULL) {
131 nargs++;
132 const char **tmp = realloc(argv,
133 (nargs + 1) * sizeof(const char *));
134 if (!tmp)
135 goto err;
136
137 argv = tmp;
138 argv[nargs - 1] = arg;
139 argv[nargs] = NULL;
140 }
141
142 return argv;
143 err:
144 free(argv);
145 return NULL;
146 }
147
148 /*
149 * rpmem_ssh_execv -- open ssh connection and run $RPMEMD_CMD with
150 * additional NULL-terminated list of arguments.
151 */
152 struct rpmem_ssh *
153 rpmem_ssh_execv(const struct rpmem_target_info *info, const char **argv)
154 {
155 struct rpmem_ssh *rps = calloc(1, sizeof(*rps));
156 if (!rps)
157 goto err_zalloc;
158
159 char *user_at_node = get_user_at_node(info);
160 if (!user_at_node)
161 goto err_user_node;
162
163 rps->cmd = rpmem_cmd_init();
164 if (!rps->cmd)
165 goto err_cmd_init;
166
167 char *cmd = get_cmd(argv);
168 if (!cmd)
169 goto err_cmd;
170
171 int ret = rpmem_cmd_push(rps->cmd, get_ssh());
172 if (ret)
173 goto err_push;
174
175 if (info->flags & RPMEM_HAS_SERVICE) {
176 /* port number is optional */
177 ret = rpmem_cmd_push(rps->cmd, "-p");
178 if (ret)
179 goto err_push;
180 ret = rpmem_cmd_push(rps->cmd, info->service);
181 if (ret)
182 goto err_push;
183 }
184
185 /*
186 * Disable allocating pseudo-terminal in order to transfer binary
187 * data safely.
188 */
189 ret = rpmem_cmd_push(rps->cmd, "-T");
190 if (ret)
191 goto err_push;
192
193 if (info->flags & RPMEM_FLAGS_USE_IPV4) {
194 ret = rpmem_cmd_push(rps->cmd, "-4");
195 if (ret)
196 goto err_push;
197 }
198
199 /* fail if password required for authentication */
200 ret = rpmem_cmd_push(rps->cmd, "-oBatchMode=yes");
201 if (ret)
202 goto err_push;
203
204 ret = rpmem_cmd_push(rps->cmd, user_at_node);
205 if (ret)
206 goto err_push;
207
208 ret = rpmem_cmd_push(rps->cmd, cmd);
209 if (ret)
210 goto err_push;
211
212 ret = rpmem_cmd_run(rps->cmd);
213 if (ret)
214 goto err_run;
215
216 free(user_at_node);
217 free(cmd);
218
219 return rps;
220 err_run:
221 err_push:
222 free(cmd);
223 err_cmd:
224 rpmem_cmd_fini(rps->cmd);
225 err_cmd_init:
226 free(user_at_node);
227 err_user_node:
228 free(rps);
229 err_zalloc:
230 return NULL;
231 }
232
233 /*
234 * rpmem_ssh_exec -- open ssh connection and run $RPMEMD_CMD with
235 * additional NULL-terminated list of arguments.
236 */
237 struct rpmem_ssh *
238 rpmem_ssh_exec(const struct rpmem_target_info *info, ...)
239 {
240 struct rpmem_ssh *ssh;
241
242 va_list args;
243 va_start(args, info);
244
245 const char **argv = valist_to_argv(args);
246 if (argv)
247 ssh = rpmem_ssh_execv(info, argv);
248 else
249 ssh = NULL;
250
251 va_end(args);
252
253 free(argv);
254
255 return ssh;
256 }
257
258 /*
259 * rpmem_ssh_open -- open ssh connection with specified node and wait for status
260 */
261 struct rpmem_ssh *
262 rpmem_ssh_open(const struct rpmem_target_info *info)
263 {
264 struct rpmem_ssh *ssh = rpmem_ssh_exec(info, NULL);
265 if (!ssh)
266 return NULL;
267
268 /*
269 * Read initial status from invoked command.
270 * This is for synchronization purposes and to make it possible
271 * to inform client that command's initialization failed.
272 */
273 int32_t status;
274 int ret = rpmem_ssh_recv(ssh, &status, sizeof(status));
275 if (ret) {
276 if (ret == 1 || errno == ECONNRESET)
277 ERR("%s", rpmem_ssh_strerror(ssh, errno));
278 else
279 ERR("!%s", info->node);
280 goto err_recv_status;
281 }
282
283 if (status) {
284 ERR("%s: unexpected status received -- '%d'",
285 info->node, status);
286 errno = status;
287 goto err_status;
288 }
289
290 RPMEM_LOG(INFO, "received status: %u", status);
291
292 return ssh;
293 err_recv_status:
294 err_status:
295 rpmem_ssh_close(ssh);
296 return NULL;
297 }
298
299 /*
300 * rpmem_ssh_close -- close ssh connection
301 */
302 int
303 rpmem_ssh_close(struct rpmem_ssh *rps)
304 {
305 int ret, rv;
306
307 rpmem_cmd_term(rps->cmd);
308 rv = rpmem_cmd_wait(rps->cmd, &ret);
309 if (rv)
310 return rv;
311
312 rpmem_cmd_fini(rps->cmd);
313 free(rps);
314
315 if (WIFEXITED(ret))
316 return WEXITSTATUS(ret);
317
318 if (WIFSIGNALED(ret)) {
319 ERR("signal received -- %d", WTERMSIG(ret));
320 return -1;
321 }
322
323 ERR("exit status -- %d", WEXITSTATUS(ret));
324
325 return -1;
326 }
327
328 /*
329 * rpmem_ssh_send -- send data using ssh transport layer
330 *
331 * The data is encoded using base64.
332 */
333 int
334 rpmem_ssh_send(struct rpmem_ssh *rps, const void *buff, size_t len)
335 {
336 int ret = rpmem_xwrite(rps->cmd->fd_in, buff, len, MSG_NOSIGNAL);
337 if (ret == 1) {
338 errno = ECONNRESET;
339 } else if (ret < 0) {
340 if (errno == EPIPE)
341 errno = ECONNRESET;
342 }
343
344 return ret;
345 }
346
347 /*
348 * rpmem_ssh_recv -- receive data using ssh transport layer
349 *
350 * The received data is decoded using base64.
351 */
352 int
353 rpmem_ssh_recv(struct rpmem_ssh *rps, void *buff, size_t len)
354 {
355 int ret = rpmem_xread(rps->cmd->fd_out, buff,
356 len, MSG_NOSIGNAL);
357 if (ret == 1) {
358 errno = ECONNRESET;
359 } else if (ret < 0) {
360 if (errno == EPIPE)
361 errno = ECONNRESET;
362 }
363
364 return ret;
365 }
366
367 /*
368 * rpmem_ssh_monitor -- check connection state of ssh
369 *
370 * Return value:
371 * 0 - disconnected
372 * 1 - connected
373 * <0 - error
374 */
375 int
376 rpmem_ssh_monitor(struct rpmem_ssh *rps, int nonblock)
377 {
378 uint32_t buff;
379 int flags = MSG_PEEK;
380 if (nonblock)
381 flags |= MSG_DONTWAIT;
382
383 int ret = rpmem_xread(rps->cmd->fd_out, &buff, sizeof(buff), flags);
384
385 if (!ret) {
386 errno = EPROTO;
387 return -1;
388 }
389
390 if (ret < 0) {
391 if (errno == EAGAIN || errno == EWOULDBLOCK)
392 return 1;
393 else
394 return ret;
395 }
396
397 return 0;
398 }
399
400 /*
401 * rpmem_ssh_strerror -- read error using stderr channel
402 */
403 const char *
404 rpmem_ssh_strerror(struct rpmem_ssh *rps, int oerrno)
405 {
406 size_t len = 0;
407 ssize_t ret;
408 while ((ret = read(rps->cmd->fd_err, error_str + len,
409 ERR_BUFF_LEN - len))) {
410 if (ret < 0)
411 return "reading error string failed";
412
413 len += (size_t)ret;
414 }
415 error_str[len] = '\0';
416
417 if (len == 0) {
418 int ret;
419 if (oerrno) {
420 char buff[UTIL_MAX_ERR_MSG];
421 util_strerror(oerrno, buff, UTIL_MAX_ERR_MSG);
422 ret = util_snprintf(error_str, ERR_BUFF_LEN,
423 "%s", buff);
424 } else {
425 ret = util_snprintf(error_str, ERR_BUFF_LEN,
426 "unknown error");
427 }
428 if (ret < 0)
429 FATAL("!snprintf");
430 } else {
431 /* get rid of new line and carriage return chars */
432 char *cr = strchr(error_str, '\r');
433 if (cr)
434 *cr = '\0';
435
436 char *nl = strchr(error_str, '\n');
437 if (nl)
438 *nl = '\0';
439 }
440
441 return error_str;
442 }