6db348b46c1b91f3b2bbcbd40673fa3d06bbfcbd
[multipath-tools/.git] / libdmmp / libdmmp.c
1 /*
2  * Copyright (C) 2015 - 2016 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 <string.h>
23 #include <sys/time.h>
24 #include <sys/resource.h>
25 #include <libudev.h>
26 #include <errno.h>
27 #include <libdevmapper.h>
28 #include <stdbool.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
49 struct dmmp_context {
50         void (*log_func)(struct dmmp_context *ctx, int priority,
51                          const char *file, int line, const char *func_name,
52                          const char *format, va_list args);
53         int log_priority;
54         void *userdata;
55         unsigned int tmo;
56 };
57
58 /*
59  * The multipathd daemon are using "uxsock_timeout" to define timeout value,
60  * if timeout at daemon side, we will get message "timeout\n".
61  * To unify this timeout with `dmmp_context_timeout_set()`, this function
62  * will keep retry mpath_process_cmd() tile meet the time of
63  * dmmp_context_timeout_get().
64  * Need to free `*output` string manually.
65  */
66 static int _process_cmd(struct dmmp_context *ctx, int fd, const char *cmd,
67                         char **output);
68
69 _dmmp_getter_func_gen(dmmp_context_log_priority_get,
70                       struct dmmp_context, ctx, log_priority,
71                       int);
72
73 _dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx,
74                       userdata, void *);
75
76 _dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
77                       unsigned int);
78
79 _dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
80                           _dmmp_mpath_free);
81
82 void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file,
83                int line, const char *func_name, const char *format, ...)
84 {
85         va_list args;
86
87         va_start(args, format);
88         ctx->log_func(ctx, priority, file, line, func_name, format, args);
89         va_end(args);
90 }
91
92 struct dmmp_context *dmmp_context_new(void)
93 {
94         struct dmmp_context *ctx = NULL;
95
96         ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context));
97
98         if (ctx == NULL)
99                 return NULL;
100
101         ctx->log_func = _dmmp_log_stderr;
102         ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT;
103         ctx->userdata = NULL;
104         ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT;
105
106         return ctx;
107 }
108
109 void dmmp_context_free(struct dmmp_context *ctx)
110 {
111         free(ctx);
112 }
113
114 void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority)
115 {
116         assert(ctx != NULL);
117         ctx->log_priority = priority;
118 }
119
120 void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo)
121 {
122         assert(ctx != NULL);
123         ctx->tmo = tmo;
124 }
125
126 void dmmp_context_log_func_set
127         (struct dmmp_context *ctx,
128          void (*log_func)(struct dmmp_context *ctx, int priority,
129                           const char *file, int line, const char *func_name,
130                           const char *format, va_list args))
131 {
132         assert(ctx != NULL);
133         ctx->log_func = log_func;
134 }
135
136 void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata)
137 {
138         assert(ctx != NULL);
139         ctx->userdata = userdata;
140 }
141
142 int dmmp_mpath_array_get(struct dmmp_context *ctx,
143                          struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count)
144 {
145         struct dmmp_mpath *dmmp_mp = NULL;
146         int rc = DMMP_OK;
147         char *j_str = NULL;
148         json_object *j_obj = NULL;
149         json_object *j_obj_map = NULL;
150         enum json_tokener_error j_err = json_tokener_success;
151         json_tokener *j_token = NULL;
152         struct array_list *ar_maps = NULL;
153         uint32_t i = 0;
154         int cur_json_major_version = -1;
155         int ar_maps_len = -1;
156         int socket_fd = -1;
157         int errno_save = 0;
158         char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
159
160         assert(ctx != NULL);
161         assert(dmmp_mps != NULL);
162         assert(dmmp_mp_count != NULL);
163
164         *dmmp_mps = NULL;
165         *dmmp_mp_count = 0;
166
167         socket_fd = mpath_connect();
168         if (socket_fd == -1) {
169                 errno_save = errno;
170                 memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
171                 strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
172                 if (errno_save == ECONNREFUSED) {
173                         rc = DMMP_ERR_NO_DAEMON;
174                         _error(ctx, "Socket connection refuse. "
175                                "Maybe multipathd daemon is not running");
176                 } else {
177                         _error(ctx, "IPC failed with error %d(%s)", errno_save,
178                                errno_str_buff);
179                         rc = DMMP_ERR_IPC_ERROR;
180                 }
181                 goto out;
182         }
183
184         _good(_process_cmd(ctx, socket_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 (socket_fd >= 0)
262                 mpath_disconnect(socket_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 miliseconds",
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 out:
368         if (rc != DMMP_OK) {
369                 free(*output);
370                 *output = NULL;
371         }
372         return rc;
373 }