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