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