2 * Copyright (C) 2015 - 2017 Red Hat, Inc.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * Author: Gris Ge <fge@redhat.com>
18 * Todd Gill <tgill@redhat.com>
25 #include <sys/resource.h>
28 #include <libdevmapper.h>
34 #include <mpath_cmd.h>
36 #include "libdmmp/libdmmp.h"
37 #include "libdmmp_private.h"
39 #define _DEFAULT_UXSOCK_TIMEOUT 60000
40 /* ^ 60 seconds. On system with 10k sdX, dmmp_mpath_array_get()
41 * only take 3.5 seconds, so this default value should be OK for most users.
44 #define _DMMP_IPC_SHOW_JSON_CMD "show maps json"
45 #define _DMMP_JSON_MAJOR_KEY "major_version"
46 #define _DMMP_JSON_MAJOR_VERSION 0
47 #define _DMMP_JSON_MAPS_KEY "maps"
48 #define _ERRNO_STR_BUFF_SIZE 256
49 #define _IPC_MAX_CMD_LEN 512
50 /* ^ Was _MAX_CMD_LEN in ./libmultipath/uxsock.h */
51 #define _LAST_ERR_MSG_BUFF_SIZE 1024
54 void (*log_func)(struct dmmp_context *ctx, int priority,
55 const char *file, int line, const char *func_name,
56 const char *format, va_list args);
60 char last_err_msg[_LAST_ERR_MSG_BUFF_SIZE];
64 * The multipathd daemon are using "uxsock_timeout" to define timeout value,
65 * if timeout at daemon side, we will get message "timeout\n".
66 * To unify this timeout with `dmmp_context_timeout_set()`, this function
67 * will keep retry mpath_process_cmd() tile meet the time of
68 * dmmp_context_timeout_get().
69 * Need to free `*output` string manually.
71 static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd,
74 static int _ipc_connect(struct dmmp_context *ctx, int *fd);
76 _dmmp_getter_func_gen(dmmp_context_log_priority_get,
77 struct dmmp_context, ctx, log_priority,
80 _dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx,
83 _dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
86 _dmmp_getter_func_gen(dmmp_last_error_msg, struct dmmp_context, ctx,
87 last_err_msg, const char *);
89 _dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
92 void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file,
93 int line, const char *func_name, const char *format, ...)
97 if (ctx->log_func == NULL)
100 va_start(args, format);
101 ctx->log_func(ctx, priority, file, line, func_name, format, args);
102 if (priority == DMMP_LOG_PRIORITY_ERROR)
103 vsnprintf(ctx->last_err_msg, _LAST_ERR_MSG_BUFF_SIZE,
108 struct dmmp_context *dmmp_context_new(void)
110 struct dmmp_context *ctx = NULL;
112 ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context));
117 ctx->log_func = _dmmp_log_stderr;
118 ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT;
119 ctx->userdata = NULL;
120 ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT;
121 memset(ctx->last_err_msg, 0, _LAST_ERR_MSG_BUFF_SIZE);
126 void dmmp_context_free(struct dmmp_context *ctx)
131 void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority)
134 ctx->log_priority = priority;
137 void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo)
143 void dmmp_context_log_func_set
144 (struct dmmp_context *ctx,
145 void (*log_func)(struct dmmp_context *ctx, int priority,
146 const char *file, int line, const char *func_name,
147 const char *format, va_list args))
150 ctx->log_func = log_func;
153 void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata)
156 ctx->userdata = userdata;
159 int dmmp_mpath_array_get(struct dmmp_context *ctx,
160 struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count)
162 struct dmmp_mpath *dmmp_mp = NULL;
165 json_object *j_obj = NULL;
166 json_object *j_obj_map = NULL;
167 enum json_tokener_error j_err = json_tokener_success;
168 json_tokener *j_token = NULL;
169 struct array_list *ar_maps = NULL;
171 int cur_json_major_version = -1;
172 int ar_maps_len = -1;
176 assert(dmmp_mps != NULL);
177 assert(dmmp_mp_count != NULL);
182 _good(_ipc_connect(ctx, &ipc_fd), rc, out);
184 _good(_process_cmd(ctx, ipc_fd, _DMMP_IPC_SHOW_JSON_CMD, &j_str),
187 _debug(ctx, "Got json output from multipathd: '%s'", j_str);
189 j_token = json_tokener_new();
190 if (j_token == NULL) {
192 _error(ctx, "BUG: json_tokener_new() retuned NULL");
195 j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1);
198 rc = DMMP_ERR_IPC_ERROR;
199 j_err = json_tokener_get_error(j_token);
200 _error(ctx, "Failed to parse JSON output from multipathd IPC: "
201 "%s", json_tokener_error_desc(j_err));
205 _json_obj_get_value(ctx, j_obj, cur_json_major_version,
206 _DMMP_JSON_MAJOR_KEY, json_type_int,
207 json_object_get_int, rc, out);
209 if (cur_json_major_version != _DMMP_JSON_MAJOR_VERSION) {
210 rc = DMMP_ERR_INCOMPATIBLE;
211 _error(ctx, "Incompatible multipathd JSON major version %d, "
212 "should be %d", cur_json_major_version,
213 _DMMP_JSON_MAJOR_VERSION);
216 _debug(ctx, "multipathd JSON major version(%d) check pass",
217 _DMMP_JSON_MAJOR_VERSION);
219 _json_obj_get_value(ctx, j_obj, ar_maps, _DMMP_JSON_MAPS_KEY,
220 json_type_array, json_object_get_array, rc, out);
222 if (ar_maps == NULL) {
224 _error(ctx, "BUG: Got NULL map array from "
225 "_json_obj_get_value()");
229 ar_maps_len = array_list_length(ar_maps);
230 if (ar_maps_len < 0) {
232 _error(ctx, "BUG: Got negative length for ar_maps");
235 else if (ar_maps_len == 0)
238 *dmmp_mp_count = ar_maps_len & UINT32_MAX;
240 *dmmp_mps = (struct dmmp_mpath **)
241 malloc(sizeof(struct dmmp_mpath *) * (*dmmp_mp_count));
242 _dmmp_alloc_null_check(ctx, dmmp_mps, rc, out);
243 for (; i < *dmmp_mp_count; ++i)
244 (*dmmp_mps)[i] = NULL;
246 for (i = 0; i < *dmmp_mp_count; ++i) {
247 j_obj_map = array_list_get_idx(ar_maps, i);
248 if (j_obj_map == NULL) {
250 _error(ctx, "BUG: array_list_get_idx() return NULL");
254 dmmp_mp = _dmmp_mpath_new();
255 _dmmp_alloc_null_check(ctx, dmmp_mp, rc, out);
256 (*dmmp_mps)[i] = dmmp_mp;
257 _good(_dmmp_mpath_update(ctx, dmmp_mp, j_obj_map), rc, out);
262 mpath_disconnect(ipc_fd);
265 json_tokener_free(j_token);
267 json_object_put(j_obj);
270 dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count);
278 static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd,
283 char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
284 struct timespec start_ts;
285 struct timespec cur_ts;
286 unsigned int ipc_tmo = 0;
287 bool flag_check_tmo = false;
288 unsigned int elapsed = 0;
290 assert(output != NULL);
296 if (clock_gettime(CLOCK_MONOTONIC, &start_ts) != 0) {
297 _error(ctx, "BUG: Failed to get CLOCK_MONOTONIC time "
298 "via clock_gettime(), error %d", errno);
304 ipc_tmo = _DEFAULT_UXSOCK_TIMEOUT;
307 _debug(ctx, "Invoking IPC command '%s' with IPC tmo %u milliseconds",
309 flag_check_tmo = false;
310 if (mpath_process_cmd(fd, cmd, output, ipc_tmo) != 0) {
312 memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
313 strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
314 if (errno_save == ETIMEDOUT) {
315 flag_check_tmo = true;
317 _error(ctx, "IPC failed when process command '%s' with "
318 "error %d(%s)", cmd, errno_save, errno_str_buff);
319 _debug(ctx, "%s", *output);
320 rc = DMMP_ERR_IPC_ERROR;
324 if ((*output != NULL) &&
325 (strncmp(*output, "timeout", strlen("timeout")) == 0))
326 flag_check_tmo = true;
328 if (flag_check_tmo == true) {
332 _debug(ctx, "IPC timeout, but user requested infinite "
337 if (clock_gettime(CLOCK_MONOTONIC, &cur_ts) != 0) {
338 _error(ctx, "BUG: Failed to get CLOCK_MONOTONIC time "
339 "via clock_gettime(), error %d", errno);
343 elapsed = (cur_ts.tv_sec - start_ts.tv_sec) * 1000 +
344 (cur_ts.tv_nsec - start_ts.tv_nsec) / 1000000;
346 if (elapsed >= ctx->tmo) {
347 rc = DMMP_ERR_IPC_TIMEOUT;
348 _error(ctx, "Timeout, try to increase it via "
349 "dmmp_context_timeout_set()");
353 ipc_tmo = ctx->tmo - elapsed;
355 _debug(ctx, "IPC timeout, but user requested timeout has not "
356 "reached yet, still have %u milliseconds", ipc_tmo);
359 if ((*output == NULL) || (strlen(*output) == 0)) {
360 _error(ctx, "IPC return empty reply for command %s",
362 rc = DMMP_ERR_IPC_ERROR;
367 if ((*output != NULL) &&
368 strncmp(*output, "permission deny",
369 strlen("permission deny")) == 0) {
370 _error(ctx, "Permission deny, need to be root");
371 rc = DMMP_ERR_PERMISSION_DENY;
383 static int _ipc_connect(struct dmmp_context *ctx, int *fd)
387 char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
394 *fd = mpath_connect();
397 memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
398 strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
399 if (errno_save == ECONNREFUSED) {
400 rc = DMMP_ERR_NO_DAEMON;
401 _error(ctx, "Socket connection refuse. "
402 "Maybe multipathd daemon is not running");
404 _error(ctx, "IPC failed with error %d(%s)", errno_save,
406 rc = DMMP_ERR_IPC_ERROR;
412 int dmmp_flush_mpath(struct dmmp_context *ctx, const char *mpath_name)
415 struct dmmp_mpath **dmmp_mps = NULL;
416 uint32_t dmmp_mp_count = 0;
420 char cmd[_IPC_MAX_CMD_LEN];
424 assert(mpath_name != NULL);
426 snprintf(cmd, _IPC_MAX_CMD_LEN, "del map %s", mpath_name);
427 if (strlen(cmd) == _IPC_MAX_CMD_LEN - 1) {
428 rc = DMMP_ERR_INVALID_ARGUMENT;
429 _error(ctx, "Invalid mpath name %s", mpath_name);
433 _good(_ipc_connect(ctx, &ipc_fd), rc, out);
434 _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out);
436 /* _process_cmd() already make sure output is not NULL */
438 if (strncmp(output, "fail", strlen("fail")) == 0) {
439 /* Check whether specified mpath exits */
440 _good(dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count),
443 for (i = 0; i < dmmp_mp_count; ++i) {
444 if (strcmp(dmmp_mpath_name_get(dmmp_mps[i]),
451 if (found == false) {
452 rc = DMMP_ERR_MPATH_NOT_FOUND;
453 _error(ctx, "Specified mpath %s not found", mpath_name);
457 rc = DMMP_ERR_MPATH_BUSY;
458 _error(ctx, "Specified mpath is in use");
459 } else if (strncmp(output, "ok", strlen("ok")) != 0) {
461 _error(ctx, "Got unexpected output for cmd '%s': '%s'",
467 mpath_disconnect(ipc_fd);
468 dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
473 int dmmp_reconfig(struct dmmp_context *ctx)
478 char cmd[_IPC_MAX_CMD_LEN];
480 snprintf(cmd, _IPC_MAX_CMD_LEN, "%s", "reconfigure");
482 _good(_ipc_connect(ctx, &ipc_fd), rc, out);
483 _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out);
487 mpath_disconnect(ipc_fd);