libdmmp: don't disconnect from multipathd twice
[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 <mpath_cmd.h>
33
34 #include "libdmmp/libdmmp.h"
35 #include "libdmmp_private.h"
36
37 #define _DEFAULT_UXSOCK_TIMEOUT         60000
38 /* ^ 60 seconds. On system with 10k sdX, dmmp_mpath_array_get()
39  *   only take 3.5 seconds, so this default value should be OK for most users.
40  */
41
42 #define _DMMP_IPC_SHOW_JSON_CMD                 "show maps json"
43 #define _DMMP_JSON_MAJOR_KEY                    "major_version"
44 #define _DMMP_JSON_MAJOR_VERSION                0
45 #define _DMMP_JSON_MAPS_KEY                     "maps"
46 #define _ERRNO_STR_BUFF_SIZE                    256
47
48 struct dmmp_context {
49         void (*log_func)(struct dmmp_context *ctx, int priority,
50                          const char *file, int line, const char *func_name,
51                          const char *format, va_list args);
52         int log_priority;
53         void *userdata;
54         unsigned int tmo;
55 };
56
57 _dmmp_getter_func_gen(dmmp_context_log_priority_get,
58                       struct dmmp_context, ctx, log_priority,
59                       int);
60
61 _dmmp_getter_func_gen(dmmp_context_userdata_get, struct dmmp_context, ctx,
62                       userdata, void *);
63
64 _dmmp_getter_func_gen(dmmp_context_timeout_get, struct dmmp_context, ctx, tmo,
65                       unsigned int);
66
67 _dmmp_array_free_func_gen(dmmp_mpath_array_free, struct dmmp_mpath,
68                           _dmmp_mpath_free);
69
70 void _dmmp_log(struct dmmp_context *ctx, int priority, const char *file,
71                int line, const char *func_name, const char *format, ...)
72 {
73         va_list args;
74
75         va_start(args, format);
76         ctx->log_func(ctx, priority, file, line, func_name, format, args);
77         va_end(args);
78 }
79
80 struct dmmp_context *dmmp_context_new(void)
81 {
82         struct dmmp_context *ctx = NULL;
83
84         ctx = (struct dmmp_context *) malloc(sizeof(struct dmmp_context));
85
86         if (ctx == NULL)
87                 return NULL;
88
89         ctx->log_func = _dmmp_log_stderr;
90         ctx->log_priority = DMMP_LOG_PRIORITY_DEFAULT;
91         ctx->userdata = NULL;
92         ctx->tmo = _DEFAULT_UXSOCK_TIMEOUT;
93
94         return ctx;
95 }
96
97 void dmmp_context_free(struct dmmp_context *ctx)
98 {
99         free(ctx);
100 }
101
102 void dmmp_context_log_priority_set(struct dmmp_context *ctx, int priority)
103 {
104         assert(ctx != NULL);
105         ctx->log_priority = priority;
106 }
107
108 void dmmp_context_timeout_set(struct dmmp_context *ctx, unsigned int tmo)
109 {
110         assert(ctx != NULL);
111         ctx->tmo = tmo;
112 }
113
114 void dmmp_context_log_func_set
115         (struct dmmp_context *ctx,
116          void (*log_func)(struct dmmp_context *ctx, int priority,
117                           const char *file, int line, const char *func_name,
118                           const char *format, va_list args))
119 {
120         assert(ctx != NULL);
121         ctx->log_func = log_func;
122 }
123
124 void dmmp_context_userdata_set(struct dmmp_context *ctx, void *userdata)
125 {
126         assert(ctx != NULL);
127         ctx->userdata = userdata;
128 }
129
130 int dmmp_mpath_array_get(struct dmmp_context *ctx,
131                          struct dmmp_mpath ***dmmp_mps, uint32_t *dmmp_mp_count)
132 {
133         struct dmmp_mpath *dmmp_mp = NULL;
134         int rc = DMMP_OK;
135         char *j_str = NULL;
136         json_object *j_obj = NULL;
137         json_object *j_obj_map = NULL;
138         enum json_tokener_error j_err = json_tokener_success;
139         json_tokener *j_token = NULL;
140         struct array_list *ar_maps = NULL;
141         uint32_t i = 0;
142         int cur_json_major_version = -1;
143         int ar_maps_len = -1;
144         int socket_fd = -1;
145         int errno_save = 0;
146         char errno_str_buff[_ERRNO_STR_BUFF_SIZE];
147
148         assert(ctx != NULL);
149         assert(dmmp_mps != NULL);
150         assert(dmmp_mp_count != NULL);
151
152         *dmmp_mps = NULL;
153         *dmmp_mp_count = 0;
154
155         socket_fd = mpath_connect();
156         if (socket_fd == -1) {
157                 errno_save = errno;
158                 memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
159                 strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
160                 if (errno_save == ECONNREFUSED) {
161                         rc = DMMP_ERR_NO_DAEMON;
162                         _error(ctx, "Socket connection refuse. "
163                                "Maybe multipathd daemon is not running");
164                 } else {
165                         _error(ctx, "IPC failed with error %d(%s)", errno_save,
166                                errno_str_buff);
167                         rc = DMMP_ERR_IPC_ERROR;
168                 }
169                 goto out;
170         }
171
172         if (mpath_process_cmd(socket_fd, _DMMP_IPC_SHOW_JSON_CMD,
173                               &j_str, ctx->tmo) != 0) {
174                 errno_save = errno;
175                 memset(errno_str_buff, 0, _ERRNO_STR_BUFF_SIZE);
176                 strerror_r(errno_save, errno_str_buff, _ERRNO_STR_BUFF_SIZE);
177                 if (errno_save == ETIMEDOUT) {
178                         rc = DMMP_ERR_IPC_TIMEOUT;
179                         _error(ctx, "IPC communication timeout, try to "
180                                "increase it via dmmp_context_timeout_set()");
181                         goto out;
182                 }
183                 _error(ctx, "IPC failed when process command '%s' with "
184                        "error %d(%s)", _DMMP_IPC_SHOW_JSON_CMD, errno_save,
185                        errno_str_buff);
186                 rc = DMMP_ERR_IPC_ERROR;
187                 goto out;
188         }
189
190         if ((j_str == NULL) || (strlen(j_str) == 0)) {
191                 _error(ctx, "IPC return empty reply for command %s",
192                        _DMMP_IPC_SHOW_JSON_CMD);
193                 rc = DMMP_ERR_IPC_ERROR;
194                 goto out;
195         }
196
197         _debug(ctx, "Got json output from multipathd: '%s'", j_str);
198         j_token = json_tokener_new();
199         if (j_token == NULL) {
200                 rc = DMMP_ERR_BUG;
201                 _error(ctx, "BUG: json_tokener_new() retuned NULL");
202                 goto out;
203         }
204         j_obj = json_tokener_parse_ex(j_token, j_str, strlen(j_str) + 1);
205
206         if (j_obj == NULL) {
207                 rc = DMMP_ERR_IPC_ERROR;
208                 j_err = json_tokener_get_error(j_token);
209                 _error(ctx, "Failed to parse JSON output from multipathd IPC: "
210                        "%s", json_tokener_error_desc(j_err));
211                 goto out;
212         }
213
214         _json_obj_get_value(ctx, j_obj, cur_json_major_version,
215                             _DMMP_JSON_MAJOR_KEY, json_type_int,
216                             json_object_get_int, rc, out);
217
218         if (cur_json_major_version != _DMMP_JSON_MAJOR_VERSION) {
219                 rc = DMMP_ERR_INCOMPATIBLE;
220                 _error(ctx, "Incompatible multipathd JSON major version %d, "
221                        "should be %d", cur_json_major_version,
222                        _DMMP_JSON_MAJOR_VERSION);
223                 goto out;
224         }
225         _debug(ctx, "multipathd JSON major version(%d) check pass",
226                _DMMP_JSON_MAJOR_VERSION);
227
228         _json_obj_get_value(ctx, j_obj, ar_maps, _DMMP_JSON_MAPS_KEY,
229                             json_type_array, json_object_get_array, rc, out);
230
231         if (ar_maps == NULL) {
232                 rc = DMMP_ERR_BUG;
233                 _error(ctx, "BUG: Got NULL map array from "
234                        "_json_obj_get_value()");
235                 goto out;
236         }
237
238         ar_maps_len = array_list_length(ar_maps);
239         if (ar_maps_len < 0) {
240                 rc = DMMP_ERR_BUG;
241                 _error(ctx, "BUG: Got negative length for ar_maps");
242                 goto out;
243         }
244         else if (ar_maps_len == 0)
245                 goto out;
246         else
247                 *dmmp_mp_count = ar_maps_len & UINT32_MAX;
248
249         *dmmp_mps = (struct dmmp_mpath **)
250                 malloc(sizeof(struct dmmp_mpath *) * (*dmmp_mp_count));
251         _dmmp_alloc_null_check(ctx, dmmp_mps, rc, out);
252         for (; i < *dmmp_mp_count; ++i)
253                 (*dmmp_mps)[i] = NULL;
254
255         for (i = 0; i < *dmmp_mp_count; ++i) {
256                 j_obj_map = array_list_get_idx(ar_maps, i);
257                 if (j_obj_map == NULL) {
258                         rc = DMMP_ERR_BUG;
259                         _error(ctx, "BUG: array_list_get_idx() return NULL");
260                         goto out;
261                 }
262
263                 dmmp_mp = _dmmp_mpath_new();
264                 _dmmp_alloc_null_check(ctx, dmmp_mp, rc, out);
265                 (*dmmp_mps)[i] = dmmp_mp;
266                 _good(_dmmp_mpath_update(ctx, dmmp_mp, j_obj_map), rc, out);
267         }
268
269 out:
270         if (socket_fd >= 0)
271                 mpath_disconnect(socket_fd);
272         free(j_str);
273         if (j_token != NULL)
274                 json_tokener_free(j_token);
275         if (j_obj != NULL)
276                 json_object_put(j_obj);
277
278         if (rc != DMMP_OK) {
279                 dmmp_mpath_array_free(*dmmp_mps, *dmmp_mp_count);
280                 *dmmp_mps = NULL;
281                 *dmmp_mp_count = 0;
282         }
283
284         return rc;
285 }