74cdb0a6da601a9858fa3b4786a93c4e181afb5f
[multipath-tools/.git] / libdmmp / libdmmp.c
1 /*
2  * Copyright (C) 2015 - 2017 Red Hat, Inc.
3  *
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.
8  *
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.
13  *
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/>.
16  *
17  * Author: Gris Ge <fge@redhat.com>
18  *         Todd Gill <tgill@redhat.com>
19  */
20
21 #include <stdint.h>
22 #include <stdbool.h>
23 #include <string.h>
24 #include <sys/time.h>
25 #include <sys/resource.h>
26 #include <libudev.h>
27 #include <errno.h>
28 #include <libdevmapper.h>
29 #include <stdbool.h>
30 #include <unistd.h>
31 #include <assert.h>
32 #include <json.h>
33 #include <time.h>
34 #include <mpath_cmd.h>
35
36 #include "libdmmp/libdmmp.h"
37 #include "libdmmp_private.h"
38
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.
42  */
43
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
52 struct dmmp_context {
53         void (*log_func)(struct dmmp_context *ctx, int priority,
54                          const char *file, int line, const char *func_name,
55                          const char *format, va_list args);
56         int log_priority;
57         void *userdata;
58         unsigned int tmo;
59 };
60
61 /*
62  * The multipathd daemon are using "uxsock_timeout" to define timeout value,
63  * if timeout at daemon side, we will get message "timeout\n".
64  * To unify this timeout with `dmmp_context_timeout_set()`, this function
65  * will keep retry mpath_process_cmd() tile meet the time of
66  * dmmp_context_timeout_get().
67  * Need to free `*output` string manually.
68  */
69 static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd,
70                         char **output);
71
72 static int _ipc_connect(struct dmmp_context *ctx, int *fd);
73
74 _dmmp_getter_func_gen(dmmp_context_log_priority_get,
75                       struct dmmp_context, ctx, log_priority,
76                       int);
77
78 _dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx,
79                       userdata, void *);
80
81 _dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
82                       unsigned int);
83
84 _dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
85                           _dmmp_mpath_free);
86
87 void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file,
88                int line, const char *func_name, const char *format, ...)
89 {
90         va_list args;
91
92         if (ctx->log_func == NULL)
93                 return;
94
95         va_start(args, format);
96         ctx->log_func(ctx, priority, file, line, func_name, format, args);
97         va_end(args);
98 }
99
100 struct dmmp_context *dmmp_context_new(void)
101 {
102         struct dmmp_context *ctx = NULL;
103
104         ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context));
105
106         if (ctx == NULL)
107                 return NULL;
108
109         ctx->log_func = _dmmp_log_stderr;
110         ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT;
111         ctx->userdata = NULL;
112         ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT;
113
114         return ctx;
115 }
116
117 void dmmp_context_free(struct dmmp_context *ctx)
118 {
119         free(ctx);
120 }
121
122 void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority)
123 {
124         assert(ctx != NULL);
125         ctx->log_priority = priority;
126 }
127
128 void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo)
129 {
130         assert(ctx != NULL);
131         ctx->tmo = tmo;
132 }
133
134 void dmmp_context_log_func_set
135         (struct dmmp_context *ctx,
136          void (*log_func)(struct dmmp_context *ctx, int priority,
137                           const char *file, int line, const char *func_name,
138                           const char *format, va_list args))
139 {
140         assert(ctx != NULL);
141         ctx->log_func = log_func;
142 }
143
144 void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata)
145 {
146         assert(ctx != NULL);
147         ctx->userdata = userdata;
148 }
149
150 int dmmp_mpath_array_get(struct dmmp_context *ctx,
151                          struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count)
152 {
153         struct dmmp_mpath *dmmp_mp = NULL;
154         int rc = DMMP_OK;
155         char *j_str = NULL;
156         json_object *j_obj = NULL;
157         json_object *j_obj_map = NULL;
158         enum json_tokener_error j_err = json_tokener_success;
159         json_tokener *j_token = NULL;
160         struct array_list *ar_maps = NULL;
161         uint32_t i = 0;
162         int cur_json_major_version = -1;
163         int ar_maps_len = -1;
164         int ipc_fd = -1;
165
166         assert(ctx != NULL);
167         assert(dmmp_mps != NULL);
168         assert(dmmp_mp_count != NULL);
169
170         *dmmp_mps = NULL;
171         *dmmp_mp_count = 0;
172
173         _good(_ipc_connect(ctx, &ipc_fd), rc, out);
174
175         _good(_process_cmd(ctx, ipc_fd, _DMMP_IPC_SHOW_JSON_CMD, &j_str),
176               rc, out);
177
178         _debug(ctx, "Got json output from multipathd: '%s'", j_str);
179
180         j_token = json_tokener_new();
181         if (j_token == NULL) {
182                 rc = DMMP_ERR_BUG;
183                 _error(ctx, "BUG: json_tokener_new() retuned NULL");
184                 goto out;
185         }
186         j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1);
187
188         if (j_obj == NULL) {
189                 rc = DMMP_ERR_IPC_ERROR;
190                 j_err = json_tokener_get_error(j_token);
191                 _error(ctx, "Failed to parse JSON output from multipathd IPC: "
192                        "%s", json_tokener_error_desc(j_err));
193                 goto out;
194         }
195
196         _json_obj_get_value(ctx, j_obj, cur_json_major_version,
197                             _DMMP_JSON_MAJOR_KEY, json_type_int,
198                             json_object_get_int, rc, out);
199
200         if (cur_json_major_version != _DMMP_JSON_MAJOR_VERSION) {
201                 rc = DMMP_ERR_INCOMPATIBLE;
202                 _error(ctx, "Incompatible multipathd JSON major version %d, "
203                        "should be %d", cur_json_major_version,
204                        _DMMP_JSON_MAJOR_VERSION);
205                 goto out;
206         }
207         _debug(ctx, "multipathd JSON major version(%d) check pass",
208                _DMMP_JSON_MAJOR_VERSION);
209
210         _json_obj_get_value(ctx, j_obj, ar_maps, _DMMP_JSON_MAPS_KEY,
211                             json_type_array, json_object_get_array, rc, out);
212
213         if (ar_maps == NULL) {
214                 rc = DMMP_ERR_BUG;
215                 _error(ctx, "BUG: Got NULL map array from "
216                        "_json_obj_get_value()");
217                 goto out;
218         }
219
220         ar_maps_len = array_list_length(ar_maps);
221         if (ar_maps_len < 0) {
222                 rc = DMMP_ERR_BUG;
223                 _error(ctx, "BUG: Got negative length for ar_maps");
224                 goto out;
225         }
226         else if (ar_maps_len == 0)
227                 goto out;
228         else
229                 *dmmp_mp_count = ar_maps_len & UINT32_MAX;
230
231         *dmmp_mps = (struct dmmp_mpath **)
232                 malloc(sizeof(struct dmmp_mpath *) * (*dmmp_mp_count));
233         _dmmp_alloc_null_check(ctx, dmmp_mps, rc, out);
234         for (; i < *dmmp_mp_count; ++i)
235                 (*dmmp_mps)[i] = NULL;
236
237         for (i = 0; i < *dmmp_mp_count; ++i) {
238                 j_obj_map = array_list_get_idx(ar_maps, i);
239                 if (j_obj_map == NULL) {
240                         rc = DMMP_ERR_BUG;
241                         _error(ctx, "BUG: array_list_get_idx() return NULL");
242                         goto out;
243                 }
244
245                 dmmp_mp = _dmmp_mpath_new();
246                 _dmmp_alloc_null_check(ctx, dmmp_mp, rc, out);
247                 (*dmmp_mps)[i] = dmmp_mp;
248                 _good(_dmmp_mpath_update(ctx, dmmp_mp, j_obj_map), rc, out);
249         }
250
251 out:
252         if (ipc_fd >= 0)
253                 mpath_disconnect(ipc_fd);
254         free(j_str);
255         if (j_token != NULL)
256                 json_tokener_free(j_token);
257         if (j_obj != NULL)
258                 json_object_put(j_obj);
259
260         if (rc != DMMP_OK) {
261                 dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count);
262                 *dmmp_mps = NULL;
263                 *dmmp_mp_count = 0;
264         }
265
266         return rc;
267 }
268
269 static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd,
270                         char **output)
271 {
272         int errno_save = 0;
273         int rc = DMMP_OK;
274         char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
275         struct timespec start_ts;
276         struct timespec cur_ts;
277         unsigned int ipc_tmo = 0;
278         bool flag_check_tmo = false;
279         unsigned int elapsed = 0;
280
281         assert(output != NULL);
282         assert(ctx != NULL);
283         assert(cmd != NULL);
284
285         *output = NULL;
286
287         if (clock_gettime(CLOCK_MONOTONIC, &start_ts) != 0) {
288                 _error(ctx, "BUG: Failed to get CLOCK_MONOTONIC time "
289                        "via clock_gettime(), error %d", errno);
290                 return DMMP_ERR_BUG;
291         }
292
293         ipc_tmo = ctx->tmo;
294         if (ctx->tmo == 0)
295                 ipc_tmo = _DEFAULT_UXSOCK_TIMEOUT;
296
297 invoke:
298         _debug(ctx, "Invoking IPC command '%s' with IPC tmo %u miliseconds",
299                cmd, ipc_tmo);
300         flag_check_tmo = false;
301         if (mpath_process_cmd(fd, cmd, output, ipc_tmo) != 0) {
302                 errno_save = errno;
303                 memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
304                 strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
305                 if (errno_save == ETIMEDOUT) {
306                         flag_check_tmo = true;
307                 } else {
308                         _error(ctx, "IPC failed when process command '%s' with "
309                                "error %d(%s)", cmd, errno_save, errno_str_buff);
310                         _debug(ctx, "%s", *output);
311                         rc = DMMP_ERR_IPC_ERROR;
312                         goto out;
313                 }
314         }
315         if ((*output != NULL) &&
316             (strncmp(*output, "timeout", strlen("timeout")) == 0))
317                 flag_check_tmo = true;
318
319         if (flag_check_tmo == true) {
320                 free(*output);
321                 *output = NULL;
322                 if (ctx->tmo == 0) {
323                         _debug(ctx, "IPC timeout, but user requested infinite "
324                                "timeout");
325                         goto invoke;
326                 }
327
328                 if (clock_gettime(CLOCK_MONOTONIC, &cur_ts) != 0) {
329                         _error(ctx, "BUG: Failed to get CLOCK_MONOTONIC time "
330                                "via clock_gettime(), error %d", errno);
331                         rc = DMMP_ERR_BUG;
332                         goto out;
333                 }
334                 elapsed = (cur_ts.tv_sec - start_ts.tv_sec) * 1000 +
335                         (cur_ts.tv_nsec - start_ts.tv_nsec) / 1000000;
336
337                 if (elapsed >= ctx->tmo) {
338                         rc = DMMP_ERR_IPC_TIMEOUT;
339                         _error(ctx, "Timeout, try to increase it via "
340                                "dmmp_context_timeout_set()");
341                         goto out;
342                 }
343                 if (ctx->tmo != 0)
344                         ipc_tmo = ctx->tmo - elapsed;
345
346                 _debug(ctx, "IPC timeout, but user requested timeout has not "
347                        "reached yet, still have %u milliseconds", ipc_tmo);
348                 goto invoke;
349         } else {
350                 if ((*output == NULL) || (strlen(*output) == 0)) {
351                         _error(ctx, "IPC return empty reply for command %s",
352                                cmd);
353                         rc = DMMP_ERR_IPC_ERROR;
354                         goto out;
355                 }
356         }
357
358 out:
359         if (rc != DMMP_OK) {
360                 free(*output);
361                 *output = NULL;
362         }
363         return rc;
364 }
365
366 static int _ipc_connect(struct dmmp_context *ctx, int *fd)
367 {
368         int rc = DMMP_OK;
369         int errno_save = 0;
370         char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
371
372         assert(ctx != NULL);
373         assert(fd != NULL);
374
375         *fd = -1;
376
377         *fd = mpath_connect();
378         if (*fd == -1) {
379                 errno_save = errno;
380                 memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
381                 strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
382                 if (errno_save == ECONNREFUSED) {
383                         rc = DMMP_ERR_NO_DAEMON;
384                         _error(ctx, "Socket connection refuse. "
385                                "Maybe multipathd daemon is not running");
386                 } else {
387                         _error(ctx, "IPC failed with error %d(%s)", errno_save,
388                                errno_str_buff);
389                         rc = DMMP_ERR_IPC_ERROR;
390                 }
391         }
392         return rc;
393 }
394
395 int dmmp_flush_mpath(struct dmmp_context *ctx, const char *mpath_name)
396 {
397         int rc = DMMP_OK;
398         struct dmmp_mpath **dmmp_mps = NULL;
399         uint32_t dmmp_mp_count = 0;
400         uint32_t i = 0;
401         bool found = false;
402         int ipc_fd = -1;
403         char cmd[_IPC_MAX_CMD_LEN];
404         char *output = NULL;
405
406         assert(ctx != NULL);
407         assert(mpath_name != NULL);
408
409         snprintf(cmd, _IPC_MAX_CMD_LEN, "del map %s", mpath_name);
410         if (strlen(cmd) == _IPC_MAX_CMD_LEN - 1) {
411                 rc = DMMP_ERR_INVALID_ARGUMENT;
412                 _error(ctx, "Invalid mpath name %s", mpath_name);
413                 goto out;
414         }
415
416         _good(_ipc_connect(ctx, &ipc_fd), rc, out);
417         _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out);
418
419         /* _process_cmd() already make sure output is not NULL */
420
421         if (strncmp(output, "fail", strlen("fail")) == 0) {
422                 /* Check whether specified mpath exits */
423                 _good(dmmp_mpath_array_get(ctx, &dmmp_mps, &dmmp_mp_count),
424                       rc, out);
425
426                 for (i = 0; i < dmmp_mp_count; ++i) {
427                         if (strcmp(dmmp_mpath_name_get(dmmp_mps[i]),
428                                    mpath_name) == 0) {
429                                 found = true;
430                                 break;
431                         }
432                 }
433
434                 if (found == false) {
435                         rc = DMMP_ERR_MPATH_NOT_FOUND;
436                         _error(ctx, "Specified mpath %s not found", mpath_name);
437                         goto out;
438                 }
439
440                 rc = DMMP_ERR_MPATH_BUSY;
441                 _error(ctx, "Specified mpath is in use");
442         } else if (strncmp(output, "ok", strlen("ok")) != 0) {
443                 rc = DMMP_ERR_BUG;
444                 _error(ctx, "Got unexpected output for cmd '%s': '%s'",
445                        cmd, output);
446         }
447
448 out:
449         if (ipc_fd >= 0)
450                 mpath_disconnect(ipc_fd);
451         dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
452         free(output);
453         return rc;
454 }
455
456 int dmmp_reconfig(struct dmmp_context *ctx)
457 {
458         int rc = DMMP_OK;
459         int ipc_fd = -1;
460         char *output = NULL;
461         char cmd[_IPC_MAX_CMD_LEN];
462
463         snprintf(cmd, _IPC_MAX_CMD_LEN, "%s", "reconfigure");
464
465         _good(_ipc_connect(ctx, &ipc_fd), rc, out);
466         _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out);
467
468 out:
469         if (ipc_fd >= 0)
470                 mpath_disconnect(ipc_fd);
471         free(output);
472         return rc;
473 }