aafd5099b38cbb6b495a2cbd917e6c9e1501576c
[platform/upstream/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 #define _LAST_ERR_MSG_BUFF_SIZE                 1024
52
53 struct dmmp_context {
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);
57         int log_priority;
58         void *userdata;
59         unsigned int tmo;
60         char last_err_msg[_LAST_ERR_MSG_BUFF_SIZE];
61 };
62
63 /*
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.
70  */
71 static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd,
72                         char **output);
73
74 static int _ipc_connect(struct dmmp_context *ctx, int *fd);
75
76 _dmmp_getter_func_gen(dmmp_context_log_priority_get,
77                       struct dmmp_context, ctx, log_priority,
78                       int);
79
80 _dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx,
81                       userdata, void *);
82
83 _dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
84                       unsigned int);
85
86 _dmmp_getter_func_gen(dmmp_last_error_msg, struct dmmp_context, ctx,
87                       last_err_msg, const char *);
88
89 _dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
90                           _dmmp_mpath_free);
91
92 void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file,
93                int line, const char *func_name, const char *format, ...)
94 {
95         va_list args;
96
97         if (ctx->log_func == NULL)
98                 return;
99
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,
104                           format, args);
105         va_end(args);
106 }
107
108 struct dmmp_context *dmmp_context_new(void)
109 {
110         struct dmmp_context *ctx = NULL;
111
112         ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context));
113
114         if (ctx == NULL)
115                 return NULL;
116
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);
122
123         return ctx;
124 }
125
126 void dmmp_context_free(struct dmmp_context *ctx)
127 {
128         free(ctx);
129 }
130
131 void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority)
132 {
133         assert(ctx != NULL);
134         ctx->log_priority = priority;
135 }
136
137 void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo)
138 {
139         assert(ctx != NULL);
140         ctx->tmo = tmo;
141 }
142
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))
148 {
149         assert(ctx != NULL);
150         ctx->log_func = log_func;
151 }
152
153 void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata)
154 {
155         assert(ctx != NULL);
156         ctx->userdata = userdata;
157 }
158
159 int dmmp_mpath_array_get(struct dmmp_context *ctx,
160                          struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count)
161 {
162         struct dmmp_mpath *dmmp_mp = NULL;
163         int rc = DMMP_OK;
164         char *j_str = 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;
170         uint32_t i = 0;
171         int cur_json_major_version = -1;
172         int ar_maps_len = -1;
173         int ipc_fd = -1;
174
175         assert(ctx != NULL);
176         assert(dmmp_mps != NULL);
177         assert(dmmp_mp_count != NULL);
178
179         *dmmp_mps = NULL;
180         *dmmp_mp_count = 0;
181
182         _good(_ipc_connect(ctx, &ipc_fd), rc, out);
183
184         _good(_process_cmd(ctx, ipc_fd, _DMMP_IPC_SHOW_JSON_CMD, &j_str),
185               rc, out);
186
187         _debug(ctx, "Got json output from multipathd: '%s'", j_str);
188
189         j_token = json_tokener_new();
190         if (j_token == NULL) {
191                 rc = DMMP_ERR_BUG;
192                 _error(ctx, "BUG: json_tokener_new() retuned NULL");
193                 goto out;
194         }
195         j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1);
196
197         if (j_obj == NULL) {
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));
202                 goto out;
203         }
204
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);
208
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);
214                 goto out;
215         }
216         _debug(ctx, "multipathd JSON major version(%d) check pass",
217                _DMMP_JSON_MAJOR_VERSION);
218
219         _json_obj_get_value(ctx, j_obj, ar_maps, _DMMP_JSON_MAPS_KEY,
220                             json_type_array, json_object_get_array, rc, out);
221
222         if (ar_maps == NULL) {
223                 rc = DMMP_ERR_BUG;
224                 _error(ctx, "BUG: Got NULL map array from "
225                        "_json_obj_get_value()");
226                 goto out;
227         }
228
229         ar_maps_len = array_list_length(ar_maps);
230         if (ar_maps_len < 0) {
231                 rc = DMMP_ERR_BUG;
232                 _error(ctx, "BUG: Got negative length for ar_maps");
233                 goto out;
234         }
235         else if (ar_maps_len == 0)
236                 goto out;
237         else
238                 *dmmp_mp_count = ar_maps_len & UINT32_MAX;
239
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;
245
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) {
249                         rc = DMMP_ERR_BUG;
250                         _error(ctx, "BUG: array_list_get_idx() return NULL");
251                         goto out;
252                 }
253
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);
258         }
259
260 out:
261         if (ipc_fd >= 0)
262                 mpath_disconnect(ipc_fd);
263         free(j_str);
264         if (j_token != NULL)
265                 json_tokener_free(j_token);
266         if (j_obj != NULL)
267                 json_object_put(j_obj);
268
269         if (rc != DMMP_OK) {
270                 dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count);
271                 *dmmp_mps = NULL;
272                 *dmmp_mp_count = 0;
273         }
274
275         return rc;
276 }
277
278 static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd,
279                         char **output)
280 {
281         int errno_save = 0;
282         int rc = DMMP_OK;
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;
289
290         assert(output != NULL);
291         assert(ctx != NULL);
292         assert(cmd != NULL);
293
294         *output = NULL;
295
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);
299                 return DMMP_ERR_BUG;
300         }
301
302         ipc_tmo = ctx->tmo;
303         if (ctx->tmo == 0)
304                 ipc_tmo = _DEFAULT_UXSOCK_TIMEOUT;
305
306 invoke:
307         _debug(ctx, "Invoking IPC command '%s' with IPC tmo %u milliseconds",
308                cmd, ipc_tmo);
309         flag_check_tmo = false;
310         if (mpath_process_cmd(fd, cmd, output, ipc_tmo) != 0) {
311                 errno_save = errno;
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;
316                 } else {
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;
321                         goto out;
322                 }
323         }
324         if ((*output != NULL) &&
325             (strncmp(*output, "timeout", strlen("timeout")) == 0))
326                 flag_check_tmo = true;
327
328         if (flag_check_tmo == true) {
329                 free(*output);
330                 *output = NULL;
331                 if (ctx->tmo == 0) {
332                         _debug(ctx, "IPC timeout, but user requested infinite "
333                                "timeout");
334                         goto invoke;
335                 }
336
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);
340                         rc = DMMP_ERR_BUG;
341                         goto out;
342                 }
343                 elapsed = (cur_ts.tv_sec - start_ts.tv_sec) * 1000 +
344                         (cur_ts.tv_nsec - start_ts.tv_nsec) / 1000000;
345
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()");
350                         goto out;
351                 }
352                 if (ctx->tmo != 0)
353                         ipc_tmo = ctx->tmo - elapsed;
354
355                 _debug(ctx, "IPC timeout, but user requested timeout has not "
356                        "reached yet, still have %u milliseconds", ipc_tmo);
357                 goto invoke;
358         } else {
359                 if ((*output == NULL) || (strlen(*output) == 0)) {
360                         _error(ctx, "IPC return empty reply for command %s",
361                                cmd);
362                         rc = DMMP_ERR_IPC_ERROR;
363                         goto out;
364                 }
365         }
366
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;
372                 goto out;
373         }
374
375 out:
376         if (rc != DMMP_OK) {
377                 free(*output);
378                 *output = NULL;
379         }
380         return rc;
381 }
382
383 static int _ipc_connect(struct dmmp_context *ctx, int *fd)
384 {
385         int rc = DMMP_OK;
386         int errno_save = 0;
387         char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
388
389         assert(ctx != NULL);
390         assert(fd != NULL);
391
392         *fd = -1;
393
394         *fd = mpath_connect();
395         if (*fd == -1) {
396                 errno_save = errno;
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");
403                 } else {
404                         _error(ctx, "IPC failed with error %d(%s)", errno_save,
405                                errno_str_buff);
406                         rc = DMMP_ERR_IPC_ERROR;
407                 }
408         }
409         return rc;
410 }
411
412 int dmmp_flush_mpath(struct dmmp_context *ctx, const char *mpath_name)
413 {
414         int rc = DMMP_OK;
415         struct dmmp_mpath **dmmp_mps = NULL;
416         uint32_t dmmp_mp_count = 0;
417         uint32_t i = 0;
418         bool found = false;
419         int ipc_fd = -1;
420         char cmd[_IPC_MAX_CMD_LEN];
421         char *output = NULL;
422
423         assert(ctx != NULL);
424         assert(mpath_name != NULL);
425
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);
430                 goto out;
431         }
432
433         _good(_ipc_connect(ctx, &ipc_fd), rc, out);
434         _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out);
435
436         /* _process_cmd() already make sure output is not NULL */
437
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),
441                       rc, out);
442
443                 for (i = 0; i < dmmp_mp_count; ++i) {
444                         if (strcmp(dmmp_mpath_name_get(dmmp_mps[i]),
445                                    mpath_name) == 0) {
446                                 found = true;
447                                 break;
448                         }
449                 }
450
451                 if (found == false) {
452                         rc = DMMP_ERR_MPATH_NOT_FOUND;
453                         _error(ctx, "Specified mpath %s not found", mpath_name);
454                         goto out;
455                 }
456
457                 rc = DMMP_ERR_MPATH_BUSY;
458                 _error(ctx, "Specified mpath is in use");
459         } else if (strncmp(output, "ok", strlen("ok")) != 0) {
460                 rc = DMMP_ERR_BUG;
461                 _error(ctx, "Got unexpected output for cmd '%s': '%s'",
462                        cmd, output);
463         }
464
465 out:
466         if (ipc_fd >= 0)
467                 mpath_disconnect(ipc_fd);
468         dmmp_mpath_array_free(dmmp_mps, dmmp_mp_count);
469         free(output);
470         return rc;
471 }
472
473 int dmmp_reconfig(struct dmmp_context *ctx)
474 {
475         int rc = DMMP_OK;
476         int ipc_fd = -1;
477         char *output = NULL;
478         char cmd[_IPC_MAX_CMD_LEN];
479
480         snprintf(cmd, _IPC_MAX_CMD_LEN, "%s", "reconfigure");
481
482         _good(_ipc_connect(ctx, &ipc_fd), rc, out);
483         _good(_process_cmd(ctx, ipc_fd, cmd, &output), rc, out);
484
485 out:
486         if (ipc_fd >= 0)
487                 mpath_disconnect(ipc_fd);
488         free(output);
489         return rc;
490 }