]> git.proxmox.com Git - mirror_iproute2.git/blob - tc/m_action.c
tc: jsonify actions core
[mirror_iproute2.git] / tc / m_action.c
1 /*
2 * m_action.c Action Management
3 *
4 * This program is free software; you can distribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version
7 * 2 of the License, or (at your option) any later version.
8 *
9 * Authors: J Hadi Salim (hadi@cyberus.ca)
10 *
11 * TODO:
12 * - parse to be passed a filedescriptor for logging purposes
13 *
14 */
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <fcntl.h>
20 #include <sys/socket.h>
21 #include <netinet/in.h>
22 #include <arpa/inet.h>
23 #include <string.h>
24 #include <dlfcn.h>
25
26 #include "utils.h"
27 #include "tc_common.h"
28 #include "tc_util.h"
29
30 static struct action_util *action_list;
31 #ifdef CONFIG_GACT
32 int gact_ld; /* f*ckin backward compatibility */
33 #endif
34 int tab_flush;
35
36 static void act_usage(void)
37 {
38 /*XXX: In the near future add a action->print_help to improve
39 * usability
40 * This would mean new tc will not be backward compatible
41 * with any action .so from the old days. But if someone really
42 * does that, they would know how to fix this ..
43 *
44 */
45 fprintf(stderr, "usage: tc actions <ACTSPECOP>*\n");
46 fprintf(stderr,
47 "Where: \tACTSPECOP := ACR | GD | FL\n"
48 "\tACR := add | change | replace <ACTSPEC>*\n"
49 "\tGD := get | delete | <ACTISPEC>*\n"
50 "\tFL := ls | list | flush | <ACTNAMESPEC>\n"
51 "\tACTNAMESPEC := action <ACTNAME>\n"
52 "\tACTISPEC := <ACTNAMESPEC> <INDEXSPEC>\n"
53 "\tACTSPEC := action <ACTDETAIL> [INDEXSPEC]\n"
54 "\tINDEXSPEC := index <32 bit indexvalue>\n"
55 "\tACTDETAIL := <ACTNAME> <ACTPARAMS>\n"
56 "\t\tExample ACTNAME is gact, mirred, bpf, etc\n"
57 "\t\tEach action has its own parameters (ACTPARAMS)\n"
58 "\n");
59
60 exit(-1);
61 }
62
63 static int print_noaopt(struct action_util *au, FILE *f, struct rtattr *opt)
64 {
65 if (opt && RTA_PAYLOAD(opt))
66 fprintf(f, "[Unknown action, optlen=%u] ",
67 (unsigned int) RTA_PAYLOAD(opt));
68 return 0;
69 }
70
71 static int parse_noaopt(struct action_util *au, int *argc_p, char ***argv_p, int code, struct nlmsghdr *n)
72 {
73 int argc = *argc_p;
74 char **argv = *argv_p;
75
76 if (argc) {
77 fprintf(stderr, "Unknown action \"%s\", hence option \"%s\" is unparsable\n", au->id, *argv);
78 } else {
79 fprintf(stderr, "Unknown action \"%s\"\n", au->id);
80 }
81 return -1;
82 }
83
84 static struct action_util *get_action_kind(char *str)
85 {
86 static void *aBODY;
87 void *dlh;
88 char buf[256];
89 struct action_util *a;
90 #ifdef CONFIG_GACT
91 int looked4gact = 0;
92 restart_s:
93 #endif
94 for (a = action_list; a; a = a->next) {
95 if (strcmp(a->id, str) == 0)
96 return a;
97 }
98
99 snprintf(buf, sizeof(buf), "%s/m_%s.so", get_tc_lib(), str);
100 dlh = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL);
101 if (dlh == NULL) {
102 dlh = aBODY;
103 if (dlh == NULL) {
104 dlh = aBODY = dlopen(NULL, RTLD_LAZY);
105 if (dlh == NULL)
106 goto noexist;
107 }
108 }
109
110 snprintf(buf, sizeof(buf), "%s_action_util", str);
111 a = dlsym(dlh, buf);
112 if (a == NULL)
113 goto noexist;
114
115 reg:
116 a->next = action_list;
117 action_list = a;
118 return a;
119
120 noexist:
121 #ifdef CONFIG_GACT
122 if (!looked4gact) {
123 looked4gact = 1;
124 strcpy(str, "gact");
125 goto restart_s;
126 }
127 #endif
128 a = calloc(1, sizeof(*a));
129 if (a) {
130 strncpy(a->id, "noact", 15);
131 a->parse_aopt = parse_noaopt;
132 a->print_aopt = print_noaopt;
133 goto reg;
134 }
135 return a;
136 }
137
138 static int
139 new_cmd(char **argv)
140 {
141 if ((matches(*argv, "change") == 0) ||
142 (matches(*argv, "replace") == 0) ||
143 (matches(*argv, "delete") == 0) ||
144 (matches(*argv, "get") == 0) ||
145 (matches(*argv, "add") == 0))
146 return 1;
147
148 return 0;
149
150 }
151
152 int parse_action(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
153 {
154 int argc = *argc_p;
155 char **argv = *argv_p;
156 struct rtattr *tail, *tail2;
157 char k[FILTER_NAMESZ];
158 int act_ck_len = 0;
159 int ok = 0;
160 int eap = 0; /* expect action parameters */
161
162 int ret = 0;
163 int prio = 0;
164 unsigned char act_ck[TC_COOKIE_MAX_SIZE];
165
166 if (argc <= 0)
167 return -1;
168
169 tail = tail2 = NLMSG_TAIL(n);
170
171 addattr_l(n, MAX_MSG, tca_id, NULL, 0);
172
173 while (argc > 0) {
174
175 memset(k, 0, sizeof(k));
176
177 if (strcmp(*argv, "action") == 0) {
178 argc--;
179 argv++;
180 eap = 1;
181 #ifdef CONFIG_GACT
182 if (!gact_ld) {
183 get_action_kind("gact");
184 }
185 #endif
186 continue;
187 } else if (strcmp(*argv, "flowid") == 0) {
188 break;
189 } else if (strcmp(*argv, "classid") == 0) {
190 break;
191 } else if (strcmp(*argv, "help") == 0) {
192 return -1;
193 } else if (new_cmd(argv)) {
194 goto done0;
195 } else {
196 struct action_util *a = NULL;
197
198 strncpy(k, *argv, sizeof(k) - 1);
199 eap = 0;
200 if (argc > 0) {
201 a = get_action_kind(k);
202 } else {
203 done0:
204 if (ok)
205 break;
206 else
207 goto done;
208 }
209
210 if (a == NULL) {
211 goto bad_val;
212 }
213
214 tail = NLMSG_TAIL(n);
215 addattr_l(n, MAX_MSG, ++prio, NULL, 0);
216 addattr_l(n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
217
218 ret = a->parse_aopt(a, &argc, &argv, TCA_ACT_OPTIONS,
219 n);
220
221 if (ret < 0) {
222 fprintf(stderr, "bad action parsing\n");
223 goto bad_val;
224 }
225
226 if (*argv && strcmp(*argv, "cookie") == 0) {
227 size_t slen;
228
229 NEXT_ARG();
230 slen = strlen(*argv);
231 if (slen > TC_COOKIE_MAX_SIZE * 2) {
232 char cookie_err_m[128];
233
234 snprintf(cookie_err_m, 128,
235 "%zd Max allowed size %d",
236 slen, TC_COOKIE_MAX_SIZE*2);
237 invarg(cookie_err_m, *argv);
238 }
239
240 if (hex2mem(*argv, act_ck, slen / 2) < 0)
241 invarg("cookie must be a hex string\n",
242 *argv);
243
244 act_ck_len = slen / 2;
245 argc--;
246 argv++;
247 }
248
249 if (act_ck_len)
250 addattr_l(n, MAX_MSG, TCA_ACT_COOKIE,
251 &act_ck, act_ck_len);
252
253 tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
254 ok++;
255 }
256 }
257
258 if (eap > 0) {
259 fprintf(stderr, "bad action empty %d\n", eap);
260 goto bad_val;
261 }
262
263 tail2->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail2;
264
265 done:
266 *argc_p = argc;
267 *argv_p = argv;
268 return 0;
269 bad_val:
270 /* no need to undo things, returning from here should
271 * cause enough pain */
272 fprintf(stderr, "parse_action: bad value (%d:%s)!\n", argc, *argv);
273 return -1;
274 }
275
276 static int tc_print_one_action(FILE *f, struct rtattr *arg)
277 {
278
279 struct rtattr *tb[TCA_ACT_MAX + 1];
280 int err = 0;
281 struct action_util *a = NULL;
282
283 if (arg == NULL)
284 return -1;
285
286 parse_rtattr_nested(tb, TCA_ACT_MAX, arg);
287
288 if (tb[TCA_ACT_KIND] == NULL) {
289 fprintf(stderr, "NULL Action!\n");
290 return -1;
291 }
292
293
294 a = get_action_kind(RTA_DATA(tb[TCA_ACT_KIND]));
295 if (a == NULL)
296 return err;
297
298 err = a->print_aopt(a, f, tb[TCA_ACT_OPTIONS]);
299
300 if (err < 0)
301 return err;
302
303 if (show_stats && tb[TCA_ACT_STATS]) {
304 print_string(PRINT_FP, NULL, "\tAction statistics:\n", NULL);
305 open_json_object("stats");
306 print_tcstats2_attr(f, tb[TCA_ACT_STATS], "\t", NULL);
307 close_json_object();
308 print_string(PRINT_FP, NULL, "\n", NULL);
309 }
310 if (tb[TCA_ACT_COOKIE]) {
311 int strsz = RTA_PAYLOAD(tb[TCA_ACT_COOKIE]);
312 char b1[strsz * 2 + 1];
313
314 print_string(PRINT_ANY, "cookie", "\tcookie %s\n",
315 hexstring_n2a(RTA_DATA(tb[TCA_ACT_COOKIE]),
316 strsz, b1, sizeof(b1)));
317 }
318
319 return 0;
320 }
321
322 static int
323 tc_print_action_flush(FILE *f, const struct rtattr *arg)
324 {
325
326 struct rtattr *tb[TCA_MAX + 1];
327 int err = 0;
328 struct action_util *a = NULL;
329 __u32 *delete_count = 0;
330
331 parse_rtattr_nested(tb, TCA_MAX, arg);
332
333 if (tb[TCA_KIND] == NULL) {
334 fprintf(stderr, "NULL Action!\n");
335 return -1;
336 }
337
338 a = get_action_kind(RTA_DATA(tb[TCA_KIND]));
339 if (a == NULL)
340 return err;
341
342 delete_count = RTA_DATA(tb[TCA_FCNT]);
343 fprintf(f, " %s (%d entries)\n", a->id, *delete_count);
344 tab_flush = 0;
345 return 0;
346 }
347
348 int
349 tc_print_action(FILE *f, const struct rtattr *arg, unsigned short tot_acts)
350 {
351
352 int i;
353
354 if (arg == NULL)
355 return 0;
356
357 if (!tot_acts)
358 tot_acts = TCA_ACT_MAX_PRIO;
359
360 struct rtattr *tb[tot_acts + 1];
361
362 parse_rtattr_nested(tb, tot_acts, arg);
363
364 if (tab_flush && NULL != tb[0] && NULL == tb[1])
365 return tc_print_action_flush(f, tb[0]);
366
367 open_json_array(PRINT_JSON, "actions");
368 for (i = 0; i < tot_acts; i++) {
369 if (tb[i]) {
370 open_json_object(NULL);
371 print_uint(PRINT_ANY, "order",
372 "\n\taction order %u: ", i);
373 if (tc_print_one_action(f, tb[i]) < 0) {
374 print_string(PRINT_FP, NULL,
375 "Error printing action\n", NULL);
376 }
377 close_json_object();
378 }
379
380 }
381 close_json_object();
382
383 return 0;
384 }
385
386 int print_action(const struct sockaddr_nl *who,
387 struct nlmsghdr *n,
388 void *arg)
389 {
390 FILE *fp = (FILE *)arg;
391 struct tcamsg *t = NLMSG_DATA(n);
392 int len = n->nlmsg_len;
393 __u32 *tot_acts = NULL;
394 struct rtattr *tb[TCA_ROOT_MAX+1];
395
396 len -= NLMSG_LENGTH(sizeof(*t));
397
398 if (len < 0) {
399 fprintf(stderr, "Wrong len %d\n", len);
400 return -1;
401 }
402
403 parse_rtattr(tb, TCA_ROOT_MAX, TA_RTA(t), len);
404
405 if (tb[TCA_ROOT_COUNT])
406 tot_acts = RTA_DATA(tb[TCA_ROOT_COUNT]);
407
408 fprintf(fp, "total acts %d\n", tot_acts ? *tot_acts:0);
409 if (tb[TCA_ACT_TAB] == NULL) {
410 if (n->nlmsg_type != RTM_GETACTION)
411 fprintf(stderr, "print_action: NULL kind\n");
412 return -1;
413 }
414
415 if (n->nlmsg_type == RTM_DELACTION) {
416 if (n->nlmsg_flags & NLM_F_ROOT) {
417 fprintf(fp, "Flushed table ");
418 tab_flush = 1;
419 } else {
420 fprintf(fp, "Deleted action ");
421 }
422 }
423
424 if (n->nlmsg_type == RTM_NEWACTION) {
425 if ((n->nlmsg_flags & NLM_F_CREATE) &&
426 !(n->nlmsg_flags & NLM_F_REPLACE)) {
427 fprintf(fp, "Added action ");
428 } else if (n->nlmsg_flags & NLM_F_REPLACE) {
429 fprintf(fp, "Replaced action ");
430 }
431 }
432
433
434 tc_print_action(fp, tb[TCA_ACT_TAB], tot_acts ? *tot_acts:0);
435
436 return 0;
437 }
438
439 static int tc_action_gd(int cmd, unsigned int flags, int *argc_p, char ***argv_p)
440 {
441 char k[FILTER_NAMESZ];
442 struct action_util *a = NULL;
443 int argc = *argc_p;
444 char **argv = *argv_p;
445 int prio = 0;
446 int ret = 0;
447 __u32 i = 0;
448 struct rtattr *tail;
449 struct rtattr *tail2;
450 struct nlmsghdr *ans = NULL;
451
452 struct {
453 struct nlmsghdr n;
454 struct tcamsg t;
455 char buf[MAX_MSG];
456 } req = {
457 .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
458 .n.nlmsg_flags = NLM_F_REQUEST | flags,
459 .n.nlmsg_type = cmd,
460 .t.tca_family = AF_UNSPEC,
461 };
462
463 argc -= 1;
464 argv += 1;
465
466
467 tail = NLMSG_TAIL(&req.n);
468 addattr_l(&req.n, MAX_MSG, TCA_ACT_TAB, NULL, 0);
469
470 while (argc > 0) {
471 if (strcmp(*argv, "action") == 0) {
472 argc--;
473 argv++;
474 continue;
475 } else if (strcmp(*argv, "help") == 0) {
476 return -1;
477 }
478
479 strncpy(k, *argv, sizeof(k) - 1);
480 a = get_action_kind(k);
481 if (a == NULL) {
482 fprintf(stderr, "Error: non existent action: %s\n", k);
483 ret = -1;
484 goto bad_val;
485 }
486 if (strcmp(a->id, k) != 0) {
487 fprintf(stderr, "Error: non existent action: %s\n", k);
488 ret = -1;
489 goto bad_val;
490 }
491
492 argc -= 1;
493 argv += 1;
494 if (argc <= 0) {
495 fprintf(stderr, "Error: no index specified action: %s\n", k);
496 ret = -1;
497 goto bad_val;
498 }
499
500 if (matches(*argv, "index") == 0) {
501 NEXT_ARG();
502 if (get_u32(&i, *argv, 10)) {
503 fprintf(stderr, "Illegal \"index\"\n");
504 ret = -1;
505 goto bad_val;
506 }
507 argc -= 1;
508 argv += 1;
509 } else {
510 fprintf(stderr, "Error: no index specified action: %s\n", k);
511 ret = -1;
512 goto bad_val;
513 }
514
515 tail2 = NLMSG_TAIL(&req.n);
516 addattr_l(&req.n, MAX_MSG, ++prio, NULL, 0);
517 addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
518 if (i > 0)
519 addattr32(&req.n, MAX_MSG, TCA_ACT_INDEX, i);
520 tail2->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail2;
521
522 }
523
524 tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
525
526 req.n.nlmsg_seq = rth.dump = ++rth.seq;
527
528 if (rtnl_talk(&rth, &req.n, &ans) < 0) {
529 fprintf(stderr, "We have an error talking to the kernel\n");
530 return 1;
531 }
532
533 if (cmd == RTM_GETACTION && print_action(NULL, ans, stdout) < 0) {
534 fprintf(stderr, "Dump terminated\n");
535 free(ans);
536 return 1;
537 }
538 free(ans);
539
540 *argc_p = argc;
541 *argv_p = argv;
542 bad_val:
543 return ret;
544 }
545
546 static int tc_action_modify(int cmd, unsigned int flags, int *argc_p, char ***argv_p)
547 {
548 int argc = *argc_p;
549 char **argv = *argv_p;
550 int ret = 0;
551 struct {
552 struct nlmsghdr n;
553 struct tcamsg t;
554 char buf[MAX_MSG];
555 } req = {
556 .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
557 .n.nlmsg_flags = NLM_F_REQUEST | flags,
558 .n.nlmsg_type = cmd,
559 .t.tca_family = AF_UNSPEC,
560 };
561 struct rtattr *tail = NLMSG_TAIL(&req.n);
562
563 argc -= 1;
564 argv += 1;
565 if (parse_action(&argc, &argv, TCA_ACT_TAB, &req.n)) {
566 fprintf(stderr, "Illegal \"action\"\n");
567 return -1;
568 }
569 tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
570
571 if (rtnl_talk(&rth, &req.n, NULL) < 0) {
572 fprintf(stderr, "We have an error talking to the kernel\n");
573 ret = -1;
574 }
575
576 *argc_p = argc;
577 *argv_p = argv;
578
579 return ret;
580 }
581
582 static int tc_act_list_or_flush(int *argc_p, char ***argv_p, int event)
583 {
584 struct rtattr *tail, *tail2, *tail3, *tail4;
585 int ret = 0, prio = 0, msg_size = 0;
586 struct action_util *a = NULL;
587 struct nla_bitfield32 flag_select = { 0 };
588 char **argv = *argv_p;
589 __u32 msec_since = 0;
590 int argc = *argc_p;
591 char k[FILTER_NAMESZ];
592 struct {
593 struct nlmsghdr n;
594 struct tcamsg t;
595 char buf[MAX_MSG];
596 } req = {
597 .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
598 .t.tca_family = AF_UNSPEC,
599 };
600
601 tail = NLMSG_TAIL(&req.n);
602 addattr_l(&req.n, MAX_MSG, TCA_ACT_TAB, NULL, 0);
603 tail2 = NLMSG_TAIL(&req.n);
604
605 strncpy(k, *argv, sizeof(k) - 1);
606 #ifdef CONFIG_GACT
607 if (!gact_ld) {
608 get_action_kind("gact");
609 }
610 #endif
611 a = get_action_kind(k);
612 if (a == NULL) {
613 fprintf(stderr, "bad action %s\n", k);
614 goto bad_val;
615 }
616 if (strcmp(a->id, k) != 0) {
617 fprintf(stderr, "bad action %s\n", k);
618 goto bad_val;
619 }
620 strncpy(k, *argv, sizeof(k) - 1);
621
622 argc -= 1;
623 argv += 1;
624
625 if (argc && (strcmp(*argv, "since") == 0)) {
626 NEXT_ARG();
627 if (get_u32(&msec_since, *argv, 0))
628 invarg("dump time \"since\" is invalid", *argv);
629 }
630
631 addattr_l(&req.n, MAX_MSG, ++prio, NULL, 0);
632 addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
633 tail2->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail2;
634 tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
635
636 tail3 = NLMSG_TAIL(&req.n);
637 flag_select.value |= TCA_FLAG_LARGE_DUMP_ON;
638 flag_select.selector |= TCA_FLAG_LARGE_DUMP_ON;
639 addattr_l(&req.n, MAX_MSG, TCA_ROOT_FLAGS, &flag_select,
640 sizeof(struct nla_bitfield32));
641 tail3->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail3;
642 if (msec_since) {
643 tail4 = NLMSG_TAIL(&req.n);
644 addattr32(&req.n, MAX_MSG, TCA_ROOT_TIME_DELTA, msec_since);
645 tail4->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail4;
646 }
647 msg_size = NLMSG_ALIGN(req.n.nlmsg_len) - NLMSG_ALIGN(sizeof(struct nlmsghdr));
648
649 if (event == RTM_GETACTION) {
650 if (rtnl_dump_request(&rth, event, (void *)&req.t, msg_size) < 0) {
651 perror("Cannot send dump request");
652 return 1;
653 }
654 ret = rtnl_dump_filter(&rth, print_action, stdout);
655 }
656
657 if (event == RTM_DELACTION) {
658 req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len);
659 req.n.nlmsg_type = RTM_DELACTION;
660 req.n.nlmsg_flags |= NLM_F_ROOT;
661 req.n.nlmsg_flags |= NLM_F_REQUEST;
662 if (rtnl_talk(&rth, &req.n, NULL) < 0) {
663 fprintf(stderr, "We have an error flushing\n");
664 return 1;
665 }
666
667 }
668
669 bad_val:
670
671 *argc_p = argc;
672 *argv_p = argv;
673 return ret;
674 }
675
676 int do_action(int argc, char **argv)
677 {
678
679 int ret = 0;
680
681 while (argc > 0) {
682
683 if (matches(*argv, "add") == 0) {
684 ret = tc_action_modify(RTM_NEWACTION, NLM_F_EXCL|NLM_F_CREATE, &argc, &argv);
685 } else if (matches(*argv, "change") == 0 ||
686 matches(*argv, "replace") == 0) {
687 ret = tc_action_modify(RTM_NEWACTION, NLM_F_CREATE|NLM_F_REPLACE, &argc, &argv);
688 } else if (matches(*argv, "delete") == 0) {
689 argc -= 1;
690 argv += 1;
691 ret = tc_action_gd(RTM_DELACTION, 0, &argc, &argv);
692 } else if (matches(*argv, "get") == 0) {
693 argc -= 1;
694 argv += 1;
695 ret = tc_action_gd(RTM_GETACTION, 0, &argc, &argv);
696 } else if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
697 || matches(*argv, "lst") == 0) {
698 if (argc <= 2) {
699 act_usage();
700 return -1;
701 }
702
703 argc -= 2;
704 argv += 2;
705 return tc_act_list_or_flush(&argc, &argv,
706 RTM_GETACTION);
707 } else if (matches(*argv, "flush") == 0) {
708 if (argc <= 2) {
709 act_usage();
710 return -1;
711 }
712
713 argc -= 2;
714 argv += 2;
715 return tc_act_list_or_flush(&argc, &argv,
716 RTM_DELACTION);
717 } else if (matches(*argv, "help") == 0) {
718 act_usage();
719 return -1;
720 } else {
721 fprintf(stderr, "Command \"%s\" is unknown, try \"tc actions help\".\n", *argv);
722 return -1;
723 }
724
725 if (ret < 0)
726 return -1;
727 }
728
729 return 0;
730 }