Line data Source code
1 : /* PTM Library
2 : * Copyright (C) 2015 Cumulus Networks, Inc.
3 : *
4 : * This file is part of Quagga.
5 : *
6 : * Quagga is free software; you can redistribute it and/or modify it
7 : * under the terms of the GNU General Public License as published by the
8 : * Free Software Foundation; either version 2, or (at your option) any
9 : * later version.
10 : *
11 : * Quagga is distributed in the hope that it will be useful, but
12 : * WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : * General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License along
17 : * with this program; see the file COPYING; if not, write to the Free Software
18 : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 : */
20 :
21 : #ifdef HAVE_CONFIG_H
22 : #include "config.h"
23 : #endif
24 :
25 : #include <stdio.h>
26 : #include <stdlib.h>
27 : #include <stdbool.h>
28 : #include <stddef.h>
29 : #include <string.h>
30 : #include <ctype.h>
31 : #include <unistd.h>
32 : #include <errno.h>
33 : #include <sys/socket.h>
34 : #include "csv.h"
35 : #include "ptm_lib.h"
36 :
37 : #define DEBUG_E 0
38 : #define DEBUG_V 0
39 :
40 : #define ERRLOG(fmt, ...) \
41 : do { \
42 : if (DEBUG_E) \
43 : fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
44 : __LINE__, __func__, ##__VA_ARGS__); \
45 : } while (0)
46 :
47 : #define DLOG(fmt, ...) \
48 : do { \
49 : if (DEBUG_V) \
50 : fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
51 : __LINE__, __func__, ##__VA_ARGS__); \
52 : } while (0)
53 :
54 : typedef struct ptm_lib_msg_ctxt_s {
55 : int cmd_id;
56 : csv_t *csv;
57 : ptmlib_msg_type type;
58 : } ptm_lib_msg_ctxt_t;
59 :
60 0 : static csv_record_t *_ptm_lib_encode_header(csv_t *csv, csv_record_t *rec,
61 : int msglen, int version, int type,
62 : int cmd_id, char *client_name)
63 : {
64 0 : char msglen_buf[16], vers_buf[16], type_buf[16], cmdid_buf[16];
65 0 : char client_buf[32];
66 0 : csv_record_t *rec1;
67 :
68 0 : snprintf(msglen_buf, sizeof(msglen_buf), "%4d", msglen);
69 0 : snprintf(vers_buf, sizeof(vers_buf), "%4d", version);
70 0 : snprintf(type_buf, sizeof(type_buf), "%4d", type);
71 0 : snprintf(cmdid_buf, sizeof(cmdid_buf), "%4d", cmd_id);
72 0 : snprintf(client_buf, 17, "%16.16s", client_name);
73 0 : if (rec) {
74 0 : rec1 = csv_encode_record(csv, rec, 5, msglen_buf, vers_buf,
75 : type_buf, cmdid_buf, client_buf);
76 : } else {
77 0 : rec1 = csv_encode(csv, 5, msglen_buf, vers_buf, type_buf,
78 : cmdid_buf, client_buf);
79 : }
80 0 : return (rec1);
81 : }
82 :
83 0 : static int _ptm_lib_decode_header(csv_t *csv, int *msglen, int *version,
84 : int *type, int *cmd_id, char *client_name)
85 : {
86 0 : char *hdr;
87 0 : csv_record_t *rec;
88 0 : csv_field_t *fld;
89 0 : int i, j;
90 :
91 0 : csv_decode(csv, NULL);
92 0 : rec = csv_record_iter(csv);
93 0 : if (rec == NULL) {
94 : DLOG("malformed CSV\n");
95 : return -1;
96 : }
97 0 : hdr = csv_field_iter(rec, &fld);
98 0 : if (hdr == NULL) {
99 : DLOG("malformed CSV\n");
100 : return -1;
101 : }
102 0 : *msglen = atoi(hdr);
103 0 : hdr = csv_field_iter_next(&fld);
104 0 : if (hdr == NULL) {
105 : DLOG("malformed CSV\n");
106 : return -1;
107 : }
108 0 : *version = atoi(hdr);
109 0 : hdr = csv_field_iter_next(&fld);
110 0 : if (hdr == NULL) {
111 : DLOG("malformed CSV\n");
112 : return -1;
113 : }
114 0 : *type = atoi(hdr);
115 0 : hdr = csv_field_iter_next(&fld);
116 0 : if (hdr == NULL) {
117 : DLOG("malformed CSV\n");
118 : return -1;
119 : }
120 0 : *cmd_id = atoi(hdr);
121 0 : hdr = csv_field_iter_next(&fld);
122 0 : if (hdr == NULL) {
123 : DLOG("malformed CSV\n");
124 : return -1;
125 : }
126 : /* remove leading spaces */
127 0 : for (i = j = 0; i < csv_field_len(fld); i++) {
128 0 : if (!isspace((unsigned char)hdr[i])) {
129 0 : client_name[j] = hdr[i];
130 0 : j++;
131 : }
132 : }
133 0 : client_name[j] = '\0';
134 :
135 0 : return 0;
136 : }
137 :
138 0 : int ptm_lib_append_msg(ptm_lib_handle_t *hdl, void *ctxt, const char *key,
139 : const char *val)
140 : {
141 0 : ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
142 0 : csv_t *csv;
143 0 : csv_record_t *mh_rec, *rec;
144 :
145 0 : if (!p_ctxt) {
146 : ERRLOG("%s: no context \n", __func__);
147 : return -1;
148 : }
149 :
150 0 : csv = p_ctxt->csv;
151 0 : mh_rec = csv_record_iter(csv);
152 0 : rec = csv_record_iter_next(mh_rec);
153 :
154 : /* append to the hdr record */
155 0 : rec = csv_append_record(csv, rec, 1, key);
156 0 : if (!rec) {
157 : ERRLOG("%s: Could not append key \n", __func__);
158 : return -1;
159 : }
160 :
161 0 : rec = csv_record_iter_next(rec);
162 : /* append to the data record */
163 0 : rec = csv_append_record(csv, rec, 1, val);
164 0 : if (!rec) {
165 : ERRLOG("%s: Could not append val \n", __func__);
166 : return -1;
167 : }
168 :
169 : /* update the msg hdr */
170 0 : _ptm_lib_encode_header(csv, mh_rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN),
171 0 : PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id,
172 0 : hdl->client_name);
173 :
174 0 : return 0;
175 : }
176 :
177 0 : int ptm_lib_init_msg(ptm_lib_handle_t *hdl, int cmd_id, int type, void *in_ctxt,
178 : void **out_ctxt)
179 : {
180 0 : ptm_lib_msg_ctxt_t *p_ctxt;
181 0 : ptm_lib_msg_ctxt_t *p_in_ctxt = in_ctxt;
182 0 : csv_t *csv;
183 0 : csv_record_t *rec, *d_rec;
184 :
185 : /* Initialize csv for using discrete record buffers */
186 0 : csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ);
187 :
188 0 : if (!csv) {
189 : ERRLOG("%s: Could not allocate csv \n", __func__);
190 : return -1;
191 : }
192 :
193 0 : rec = _ptm_lib_encode_header(csv, NULL, 0, PTMLIB_MSG_VERSION, type,
194 0 : cmd_id, hdl->client_name);
195 :
196 0 : if (!rec) {
197 0 : ERRLOG("%s: Could not allocate record \n", __func__);
198 0 : csv_clean(csv);
199 0 : csv_free(csv);
200 0 : return -1;
201 : }
202 :
203 0 : p_ctxt = calloc(1, sizeof(*p_ctxt));
204 0 : if (!p_ctxt) {
205 0 : ERRLOG("%s: Could not allocate context \n", __func__);
206 0 : csv_clean(csv);
207 0 : csv_free(csv);
208 0 : return -1;
209 : }
210 :
211 0 : p_ctxt->csv = csv;
212 0 : p_ctxt->cmd_id = cmd_id;
213 0 : p_ctxt->type = type;
214 :
215 0 : *(ptm_lib_msg_ctxt_t **)out_ctxt = p_ctxt;
216 :
217 : /* caller supplied a context to initialize with? */
218 0 : if (p_in_ctxt) {
219 : /* insert the hdr rec */
220 0 : rec = csv_record_iter(p_in_ctxt->csv);
221 0 : csv_clone_record(p_in_ctxt->csv, rec, &d_rec);
222 0 : csv_insert_record(csv, d_rec);
223 : /* insert the data rec */
224 0 : rec = csv_record_iter_next(rec);
225 0 : csv_clone_record(p_in_ctxt->csv, rec, &d_rec);
226 0 : csv_insert_record(csv, d_rec);
227 : }
228 : return 0;
229 : }
230 :
231 0 : int ptm_lib_cleanup_msg(ptm_lib_handle_t *hdl, void *ctxt)
232 : {
233 0 : ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
234 0 : csv_t *csv;
235 :
236 0 : if (!p_ctxt) {
237 : ERRLOG("%s: no context \n", __func__);
238 : return -1;
239 : }
240 :
241 0 : csv = p_ctxt->csv;
242 :
243 0 : csv_clean(csv);
244 0 : csv_free(csv);
245 0 : free(p_ctxt);
246 :
247 0 : return 0;
248 : }
249 :
250 0 : int ptm_lib_complete_msg(ptm_lib_handle_t *hdl, void *ctxt, char *buf, int *len)
251 : {
252 0 : ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
253 0 : csv_t *csv;
254 0 : csv_record_t *rec;
255 :
256 0 : if (!p_ctxt) {
257 : ERRLOG("%s: no context \n", __func__);
258 : return -1;
259 : }
260 :
261 0 : csv = p_ctxt->csv;
262 0 : rec = csv_record_iter(csv);
263 :
264 0 : _ptm_lib_encode_header(csv, rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN),
265 0 : PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id,
266 0 : hdl->client_name);
267 :
268 : /* parse csv contents into string */
269 0 : if (buf && len) {
270 0 : if (csv_serialize(csv, buf, *len)) {
271 : ERRLOG("%s: cannot serialize\n", __func__);
272 : return -1;
273 : }
274 0 : *len = csvlen(csv);
275 : }
276 :
277 0 : csv_clean(csv);
278 0 : csv_free(csv);
279 0 : free(p_ctxt);
280 :
281 0 : return 0;
282 : }
283 :
284 0 : int ptm_lib_find_key_in_msg(void *ctxt, const char *key, char *val)
285 : {
286 0 : ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
287 0 : csv_t *csv = p_ctxt->csv;
288 0 : csv_record_t *hrec, *drec;
289 0 : csv_field_t *hfld, *dfld;
290 0 : char *hstr, *dstr;
291 :
292 : /**
293 : * skip over ptm hdr if present
294 : * The next hdr is the keys (column name)
295 : * The next hdr is the data
296 : */
297 0 : if (csv_num_records(csv) > 2) {
298 0 : hrec = csv_record_iter(csv);
299 0 : hrec = csv_record_iter_next(hrec);
300 : } else {
301 0 : hrec = csv_record_iter(csv);
302 : }
303 0 : drec = csv_record_iter_next(hrec);
304 0 : val[0] = '\0';
305 0 : for (hstr = csv_field_iter(hrec, &hfld),
306 0 : dstr = csv_field_iter(drec, &dfld);
307 0 : (hstr && dstr); hstr = csv_field_iter_next(&hfld),
308 0 : dstr = csv_field_iter_next(&dfld)) {
309 0 : if (!strncmp(hstr, key, csv_field_len(hfld))) {
310 0 : snprintf(val, csv_field_len(dfld) + 1, "%s", dstr);
311 0 : return 0;
312 : }
313 : }
314 :
315 : return -1;
316 : }
317 :
318 0 : static int _ptm_lib_read_ptm_socket(int fd, char *buf, int len)
319 : {
320 0 : int retries = 0, rc;
321 0 : int bytes_read = 0;
322 :
323 0 : while (bytes_read != len) {
324 0 : rc = recv(fd, (void *)(buf + bytes_read), (len - bytes_read),
325 : MSG_DONTWAIT);
326 0 : if (rc <= 0) {
327 0 : if (errno && (errno != EAGAIN)
328 : && (errno != EWOULDBLOCK)) {
329 0 : ERRLOG("fatal recv error(%s), closing connection, rc %d\n",
330 : strerror(errno), rc);
331 0 : return (rc);
332 : } else {
333 0 : if (retries++ < 2) {
334 0 : usleep(10000);
335 0 : continue;
336 : }
337 0 : DLOG("max retries - recv error(%d - %s) bytes read %d (%d)\n",
338 : errno, strerror(errno), bytes_read, len);
339 0 : return (bytes_read);
340 : }
341 0 : break;
342 : } else {
343 0 : bytes_read += rc;
344 : }
345 : }
346 :
347 : return bytes_read;
348 : }
349 :
350 0 : int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen,
351 : void *arg)
352 : {
353 0 : int rc, len;
354 0 : char client_name[32];
355 0 : int cmd_id = 0, type = 0, ver = 0, msglen = 0;
356 0 : csv_t *csv;
357 0 : ptm_lib_msg_ctxt_t *p_ctxt = NULL;
358 :
359 0 : len = _ptm_lib_read_ptm_socket(fd, inbuf, PTMLIB_MSG_HDR_LEN);
360 0 : if (len <= 0)
361 : return (len);
362 :
363 0 : csv = csv_init(NULL, inbuf, PTMLIB_MSG_HDR_LEN);
364 :
365 0 : if (!csv) {
366 : DLOG("Cannot allocate csv for hdr\n");
367 : return -1;
368 : }
369 :
370 0 : rc = _ptm_lib_decode_header(csv, &msglen, &ver, &type, &cmd_id,
371 : client_name);
372 :
373 0 : csv_clean(csv);
374 0 : csv_free(csv);
375 :
376 0 : if (rc < 0) {
377 : /* could not decode the CSV - maybe its legacy cmd?
378 : * get the entire cmd from the socket and see if we can process
379 : * it
380 : */
381 0 : if (len == PTMLIB_MSG_HDR_LEN) {
382 0 : len += _ptm_lib_read_ptm_socket(
383 : fd, (inbuf + PTMLIB_MSG_HDR_LEN),
384 : inlen - PTMLIB_MSG_HDR_LEN);
385 0 : if (len <= 0)
386 : return (len);
387 : }
388 :
389 0 : inbuf[len] = '\0';
390 : /* we only support the get-status cmd */
391 0 : if (strcmp(inbuf, PTMLIB_CMD_GET_STATUS)) {
392 : DLOG("unsupported legacy cmd %s\n", inbuf);
393 : return -1;
394 : }
395 : /* internally create a csv-style cmd */
396 0 : ptm_lib_init_msg(hdl, 0, PTMLIB_MSG_TYPE_CMD, NULL,
397 : (void *)&p_ctxt);
398 0 : if (!p_ctxt) {
399 : DLOG("couldnt allocate context\n");
400 : return -1;
401 : }
402 0 : ptm_lib_append_msg(hdl, p_ctxt, "cmd", PTMLIB_CMD_GET_STATUS);
403 :
404 : } else {
405 :
406 0 : if (msglen > inlen) {
407 : DLOG("msglen [%d] > inlen [%d]\n", msglen, inlen);
408 : return -1;
409 : }
410 :
411 : /* read the rest of the msg */
412 0 : len = _ptm_lib_read_ptm_socket(fd, inbuf, msglen);
413 0 : if (len <= 0) {
414 : return (len);
415 : }
416 :
417 0 : inbuf[len] = '\0';
418 :
419 0 : csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ);
420 0 : if (!csv) {
421 : ERRLOG("Cannot allocate csv for msg\n");
422 : return -1;
423 : }
424 :
425 0 : csv_decode(csv, inbuf);
426 0 : p_ctxt = calloc(1, sizeof(*p_ctxt));
427 0 : if (!p_ctxt) {
428 0 : ERRLOG("%s: Could not allocate context \n", __func__);
429 0 : csv_clean(csv);
430 0 : csv_free(csv);
431 0 : return -1;
432 : }
433 :
434 0 : p_ctxt->csv = csv;
435 0 : p_ctxt->cmd_id = cmd_id;
436 0 : p_ctxt->type = type;
437 : }
438 :
439 0 : switch (p_ctxt->type) {
440 0 : case PTMLIB_MSG_TYPE_NOTIFICATION:
441 0 : if (hdl->notify_cb)
442 0 : hdl->notify_cb(arg, p_ctxt);
443 : break;
444 0 : case PTMLIB_MSG_TYPE_CMD:
445 0 : if (hdl->cmd_cb)
446 0 : hdl->cmd_cb(arg, p_ctxt);
447 : break;
448 0 : case PTMLIB_MSG_TYPE_RESPONSE:
449 0 : if (hdl->response_cb)
450 0 : hdl->response_cb(arg, p_ctxt);
451 : break;
452 : default:
453 : return -1;
454 : }
455 :
456 0 : csv_clean(p_ctxt->csv);
457 0 : csv_free(p_ctxt->csv);
458 0 : free(p_ctxt);
459 :
460 0 : return len;
461 : }
462 :
463 0 : ptm_lib_handle_t *ptm_lib_register(char *client_name, ptm_cmd_cb cmd_cb,
464 : ptm_notify_cb notify_cb,
465 : ptm_response_cb response_cb)
466 : {
467 0 : ptm_lib_handle_t *hdl;
468 :
469 0 : hdl = calloc(1, sizeof(*hdl));
470 :
471 0 : if (hdl) {
472 0 : strncpy(hdl->client_name, client_name, PTMLIB_MAXNAMELEN - 1);
473 0 : hdl->cmd_cb = cmd_cb;
474 0 : hdl->notify_cb = notify_cb;
475 0 : hdl->response_cb = response_cb;
476 : }
477 :
478 0 : return hdl;
479 : }
480 :
481 0 : void ptm_lib_deregister(ptm_lib_handle_t *hdl)
482 : {
483 0 : if (hdl) {
484 0 : memset(hdl, 0x00, sizeof(*hdl));
485 0 : free(hdl);
486 : }
487 0 : }
|