ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-SNMP-XS/XS.xs
(Generate patch)

Comparing Net-SNMP-XS/XS.xs (file contents):
Revision 1.2 by root, Wed Apr 8 10:39:32 2009 UTC vs.
Revision 1.24 by root, Tue May 7 21:42:36 2019 UTC

2#include "perl.h" 2#include "perl.h"
3#include "XSUB.h" 3#include "XSUB.h"
4 4
5// C99 required 5// C99 required
6 6
7#define ASN_BOOLEAN 0x01
8#define ASN_INTEGER 0x02
9#define ASN_BIT_STR 0x03
10#define ASN_OCTET_STR 0x04
11#define ASN_NULL 0x05
12#define ASN_OBJECT_ID 0x06
13#define ASN_SEQUENCE 0x10
14#define ASN_SET 0x11
15
16#define ASN_UNIVERSAL 0x00
17#define ASN_APPLICATION 0x40
18#define ASN_CONTEXT 0x80
19#define ASN_PRIVATE 0xc0
20
21#define ASN_PRIMITIVE 0x00
22#define ASN_CONSTRUCTOR 0x20
23
24#define ASN_LONG_LEN 0x80
25#define ASN_EXTENSION_ID 0x1f
26#define ASN_BIT8 0x80
27
28//#define BENCHMARK 7//#define BENCHMARK
29 8
9enum {
10 // ASN_TAG
11 ASN_BOOLEAN = 0x01,
12 ASN_INTEGER32 = 0x02,
13 ASN_BIT_STRING = 0x03,
14 ASN_OCTET_STRING = 0x04,
15 ASN_NULL = 0x05,
16 ASN_OBJECT_IDENTIFIER = 0x06,
17 ASN_SEQUENCE = 0x10,
18
19 ASN_TAG_BER = 0x1f,
20 ASN_TAG_MASK = 0x1f,
21
22 // primitive/constructed
23 ASN_CONSTRUCTED = 0x20,
24
25 // ASN_CLASS
26 ASN_UNIVERSAL = 0x00,
27 ASN_APPLICATION = 0x40,
28 ASN_CONTEXT = 0x80,
29 ASN_PRIVATE = 0xc0,
30
31 ASN_CLASS_MASK = 0xc0,
32 ASN_CLASS_SHIFT = 6,
33
34 // ASN_APPLICATION
35 ASN_IPADDRESS = 0x00,
36 ASN_COUNTER32 = 0x01,
37 ASN_UNSIGNED32 = 0x02,
38 ASN_TIMETICKS = 0x03,
39 ASN_OPAQUE = 0x04,
40 ASN_COUNTER64 = 0x06,
41};
42
43#define MAX_OID_STRLEN 4096
44
45#define HAVE_VERSIONSORT defined (_GNU_SOURCE) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
46
47static SV *cur_bufobj;
30static SV *msg; 48static SV *msg, *bufsv;
31static int errflag; 49static int errflag, leading_dot;
32static U8 *buf, *cur; 50static U8 *buf, *cur;
33static STRLEN len, rem; 51static STRLEN len, rem;
34 52
53typedef SV *BUFOBJ;
54
55/////////////////////////////////////////////////////////////////////////////
56
57#if 0
58 if (msg)
59 croak ("recursive invocation of Net::SNMP::XS parser is not supported");
60
61
62void
63clr_msg ()
64 CODE:
65 SvREFCNT_dec (msg); msg = 0;
66 buf = cur = (U8 *)"";
67 len = rem = 0;
68#endif
69
35static void 70static void
71clear_bufobj (void)
72{
73 // serialise our state back
74 if (msg && SvROK (msg))
75 {
76 SV *idx_sv = *hv_fetch ((HV *)cur_bufobj, "_index" , sizeof ("_index" ) - 1, 1);
77 sv_setiv (idx_sv, cur - buf);
78 }
79
80 SvREFCNT_dec (msg);
81 msg = 0;
82 cur_bufobj = 0;
83}
84
85static void
86switch_bufobj (BUFOBJ neu)
87{
88 clear_bufobj ();
89
90 msg = newSVsv (neu);
91 cur_bufobj = SvRV (msg);
92 sv_rvweaken (msg);
93
94 errflag = 0;
95 leading_dot = -1;
96
97 IV index = SvIV (*hv_fetch ((HV *)cur_bufobj, "_index" , sizeof ("_index" ) - 1, 1));
98 bufsv = *hv_fetch ((HV *)cur_bufobj, "_buffer", sizeof ("_buffer") - 1, 1);
99
100 buf = SvPVbyte (bufsv, len);
101 cur = buf + index;
102 rem = len - index;
103}
104
105/////////////////////////////////////////////////////////////////////////////
106
107static SV *
108x_get_cv (SV *cb_sv)
109{
110 HV *st;
111 GV *gvp;
112 CV *cv = sv_2cv (cb_sv, &st, &gvp, 0);
113
114 if (!cv)
115 croak ("CODE reference expected");
116
117 return (SV *)cv;
118}
119
120static void
36error (const char *msg) 121error (const char *errmsg)
37{ 122{
38 errflag = 1; 123 errflag = 1;
39 124
40 printf ("<<<%s>>>\n", msg);//D 125 if (!msg)
126 croak ("Net::SNMP::XS fatal error, parser called without parsing context");
127
128 dSP;
129 PUSHMARK (SP);
130 EXTEND (SP, 2);
131 PUSHs (msg);
132 PUSHs (sv_2mortal (newSVpv (errmsg, 0)));
133 PUTBACK;
134 call_method ("_error", G_VOID | G_DISCARD);
41} 135}
42 136
43static int 137static int
44need (int count) 138need (int count)
45{ 139{
134 } 228 }
135 229
136 return res; 230 return res;
137} 231}
138 232
233static U32
234process_integer32 (void)
235{
236 U32 length = process_length ();
237
238 if (length <= 0)
239 {
240 error ("INTEGER32 length equal to zero");
241 return 0;
242 }
243
244 U8 *data = getn (length, 0);
245
246 if (!data)
247 return 0;
248
249 if (length > 5 || (length > 4 && data [0]))
250 {
251 error ("INTEGER32 length too long");
252 return 0;
253 }
254
255 U32 res = data [0] & 0x80 ? 0xffffffff : 0;
256
257 while (length--)
258 res = (res << 8) | *data++;
259
260 return res;
261}
262
263static SV *
264process_integer32_sv (void)
265{
266 return newSViv ((I32)process_integer32 ());
267}
268
269static SV *
270process_unsigned32_sv (void)
271{
272 return newSVuv ((U32)process_integer32 ());
273}
274
275#if IVSIZE >= 8
276
277static U64TYPE
278process_integer64 (void)
279{
280 U32 length = process_length ();
281
282 if (length <= 0)
283 {
284 error ("INTEGER64 length equal to zero");
285 return 0;
286 }
287
288 U8 *data = getn (length, 0);
289
290 if (!data)
291 return 0;
292
293 if (length > 8 + !data [0])
294 {
295 error ("INTEGER64 length too long");
296 return 0;
297 }
298
299 U64TYPE res = data [0] & 0x80 ? -1 : 0;
300
301 while (length--)
302 res = (res << 8) | *data++;
303
304 return res;
305}
306
307static SV *
308process_integer64_sv (void)
309{
310 return newSViv ((I64TYPE)process_integer64 ());
311}
312
313static SV *
314process_unsigned64_sv (void)
315{
316 return newSVuv ((U64TYPE)process_integer64 ());
317}
318
319#endif
320
321static SV *
322process_octet_string_sv (void)
323{
324 U32 length = process_length ();
325
326 U8 *data = getn (length, 0);
327 if (!data)
328 {
329 error ("OCTET STRING too long");
330 return &PL_sv_undef;
331 }
332
333 return newSVpvn (data, length);
334}
335
336static char *
337write_uv (char *buf, U32 u)
338{
339 // the one-digit case is absolutely predominant, so this pays off (hopefully)
340 if (u < 10)
341 *buf++ = u + '0';
342 else
343 {
344 // this *could* be done much faster using branchless fixed-point arithmetics
345 char *beg = buf;
346
347 do
348 {
349 *buf++ = u % 10 + '0';
350 u /= 10;
351 }
352 while (u);
353
354 // reverse digits
355 char *ptr = buf;
356 while (--ptr > beg)
357 {
358 char c = *ptr;
359 *ptr = *beg;
360 *beg = c;
361 ++beg;
362 }
363 }
364
365 return buf;
366}
367
368static SV *
369process_object_identifier_sv (void)
370{
371 U32 length = process_length ();
372
373 if (length <= 0)
374 {
375 error ("OBJECT IDENTIFIER length equal to zero");
376 return &PL_sv_undef;
377 }
378
379 U8 *end = cur + length;
380 U32 w = getb ();
381
382 static char oid[MAX_OID_STRLEN]; // must be static
383 char *app = oid;
384
385 if (leading_dot < 0)
386 leading_dot = SvTRUE (*hv_fetch ((HV *)SvRV (msg), "_leading_dot", sizeof ("_leading_dot") - 1, 1));
387
388 *app = '.'; app += ! ! leading_dot;
389
390 {
391 UV w1, w2;
392
393 if (w < 2 * 40)
394 (w1 = w / 40), (w2 = w % 40);
395 else
396 (w1 = 2), (w2 = w - 2 * 40);
397
398 app = write_uv (app, w1);
399 *app++ = '.';
400 app = write_uv (app, w2);
401 }
402
403 // we assume an oid component is never > 64 bytes
404 while (cur < end && oid + sizeof (oid) - app > 64)
405 {
406 w = getb ();
407 *app++ = '.';
408 app = write_uv (app, w);
409 }
410
411 return newSVpvn (oid, app - oid);
412}
413
414static AV *av_type;
415
416static SV *
417process_sv (int *found)
418{
419 int type = get8 ();
420
421 *found = type;
422
423 SV *res;
424
425 switch (type)
426 {
427 case ASN_OBJECT_IDENTIFIER:
428 res = process_object_identifier_sv ();
429 break;
430
431 case ASN_INTEGER32:
432 res = process_integer32_sv ();
433 break;
434
435 case ASN_APPLICATION | ASN_UNSIGNED32:
436 case ASN_APPLICATION | ASN_COUNTER32:
437 case ASN_APPLICATION | ASN_TIMETICKS:
438 res = process_unsigned32_sv ();
439 break;
440
441 case ASN_SEQUENCE | ASN_CONSTRUCTED:
442 res = newSVuv (process_length ());
443 break;
444
445 case ASN_OCTET_STRING:
446 case ASN_APPLICATION | ASN_OPAQUE:
447 res = process_octet_string_sv ();
448 break;
449
450 default:
451 {
452 if (type > AvFILLp (av_type)
453 || AvARRAY (av_type)[type] == 0
454 || AvARRAY (av_type)[type] == &PL_sv_undef)
455 {
456 error ("Unknown ASN.1 type");
457 return &PL_sv_undef;
458 }
459
460 dSP;
461 PUSHMARK (SP);
462 EXTEND (SP, 2);
463 PUSHs (msg);
464 PUSHs (sv_2mortal (newSViv (type)));
465 PUTBACK;
466 int count = call_sv (AvARRAY (av_type)[type], G_SCALAR);
467 SPAGAIN;
468 res = count ? SvREFCNT_inc (TOPs) : &PL_sv_undef;
469 }
470 }
471
472 return errflag ? &PL_sv_undef : res;
473}
474
475/////////////////////////////////////////////////////////////////////////////
476
477#if HAVE_VERSIONSORT
478
479static int
480oid_lex_cmp (const void *a_, const void *b_)
481{
482 const char *a = SvPVX (*(SV **)a_);
483 const char *b = SvPVX (*(SV **)b_);
484
485 a += *a == '.';
486 b += *b == '.';
487
488 return strverscmp (a, b);
489}
490
491#endif
492
139MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::XS 493MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::XS
140 494
495PROTOTYPES: ENABLE
496
497BOOT:
498 av_type = newAV ();
499
141void 500void
142set_msg (SV *msg_, SV *buf_) 501set_type (int type, SV *cv)
143 CODE: 502 CODE:
144 errflag = 0; 503 cv = x_get_cv (cv);
145 msg = SvREFCNT_inc (msg_); 504 assert (SvTYPE (cv) == SVt_PVCV);
146 buf = SvPVbyte (buf_, len); 505 av_store (av_type, type, SvREFCNT_inc_NN (cv));
147 cur = buf; 506
148 rem = len; 507MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::Message
149#ifdef BENCHMARK
150 t1 = tstamp ();
151#endif
152 508
153void 509void
154clr_msg () 510_buffer_append (BUFOBJ self, SV *value)
511 ALIAS:
512 _buffer_put = 1
155 CODE: 513 PPCODE:
156 SvREFCNT_dec (msg); 514{
157 buf = cur = ""; 515 STRLEN vlen;
158 len = rem = 0; 516 const char *vstr = SvPVbyte (value, vlen);
159#ifdef BENCHMARK
160 printf ("%f\n", tstamp () - t1);//D
161#endif
162 517
163MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::Message 518 if (ix)
519 sv_insert (bufsv, 0, 0, vstr, vlen);
520 else
521 sv_catpvn (bufsv, vstr, vlen);
522
523 buf = SvPVbyte (bufsv, len);
524 cur = buf;
525 rem = len;
526
527 SV *len_sv = *hv_fetch ((HV *)cur_bufobj, "_length", sizeof ("_length") - 1, 1);
528 sv_setiv (len_sv, len);
529
530 // some callers test for defined'ness of the returnvalue. *sigh*
531 XPUSHs (&PL_sv_yes);
532}
164 533
165void 534void
166_buffer_get (SV *self, int count = -1) 535_buffer_get (BUFOBJ self, int count = -1)
167 PPCODE: 536 PPCODE:
168{ 537{
169 // grrr. 538 // grrr.
170 if (count < 0) 539 if (count < 0)
171 { 540 {
172 hv_delete ((HV *)SvRV (self), "_index" , 6, G_DISCARD); 541 hv_delete ((HV *)SvRV (self), "_index" , 6, G_DISCARD);
173 hv_delete ((HV *)SvRV (self), "_length", 7, G_DISCARD); 542 hv_delete ((HV *)SvRV (self), "_length", 7, G_DISCARD);
174 SV **svp = hv_fetch ((HV *)SvRV (self), "_buffer", 7, 1);
175 XPUSHs (sv_2mortal (newSVsv (*svp))); 543 XPUSHs (sv_2mortal (newSVsv (bufsv)));
176 sv_setpvn (*svp, "", 0); 544 sv_setpvn (bufsv, "", 0);
545
546 buf = "";
547 cur = buf;
548 rem = 0;
549
177 XSRETURN (1); 550 XSRETURN (1);
178 } 551 }
179 552
180 char *data = getn (count, 0); 553 char *data = getn (count, 0);
181 554
182 if (data) 555 if (data)
183 XPUSHs (sv_2mortal (newSVpvn (data, count))); 556 XPUSHs (sv_2mortal (newSVpvn (data, count)));
184} 557}
185 558
186U32 559U32
187index (SV *self, int ndx = -1) 560index (BUFOBJ self, int ndx = -1)
188 CODE: 561 CODE:
189{ 562{
190 if (ndx >= 0 && ndx < len) 563 if (ndx >= 0 && ndx < len)
191 { 564 {
192 cur = buf + ndx; 565 cur = buf + ndx;
193 rem = len - ndx; 566 rem = len - ndx;
194 } 567 }
195 568
196 RETVAL = cur - buf; 569 RETVAL = cur - buf;
197} 570}
198 OUTPUT: 571 OUTPUT: RETVAL
199 RETVAL
200 572
201U32 573U32
202_process_length (SV *self, ...) 574_process_length (BUFOBJ self, ...)
203 ALIAS: 575 ALIAS:
204 _process_sequence = 1 576 _process_sequence = 0
205 CODE: 577 CODE:
206 RETVAL = process_length (); 578 RETVAL = process_length ();
207 OUTPUT: 579 OUTPUT: RETVAL
208 RETVAL
209 580
210I32 581SV *
211_process_integer32 (SV *self, ...) 582_process_integer32 (BUFOBJ self, ...)
583 CODE:
584 RETVAL = process_integer32_sv ();
585 OUTPUT: RETVAL
586
587SV *
588_process_counter (BUFOBJ self, ...)
212 ALIAS: 589 ALIAS:
213 _process_counter = 0
214 _process_gauge = 0 590 _process_gauge = 0
591 _process_timeticks = 0
215 CODE: 592 CODE:
216{ 593 RETVAL = process_unsigned32_sv ();
217 U32 length = process_length (); 594 OUTPUT: RETVAL
218 595
219 if (length <= 0) 596#if IVSIZE >= 8
220 {
221 error ("INTEGER32 length equal to zero");
222 XSRETURN_UNDEF;
223 }
224
225 U8 *data = getn (length, 0);
226
227 if (!data)
228 XSRETURN_UNDEF;
229
230 if (length > 5 || (length > 4 && data [0]))
231 {
232 error ("INTEGER32 length too long");
233 XSRETURN_UNDEF;
234 }
235
236 U32 res = data [0] & 0x80 ? 0xffffffff : 0;
237
238 while (length--)
239 res = (res << 8) | *data++;
240
241 RETVAL = res;
242}
243 OUTPUT:
244 RETVAL
245 597
246SV * 598SV *
247_process_object_identifier (SV *self, ...) 599_process_counter64 (BUFOBJ self, ...)
248 CODE: 600 CODE:
249{ 601 RETVAL = process_unsigned64_sv ();
250 U32 length = process_length (); 602 OUTPUT: RETVAL
251 603
252 if (length <= 0) 604#endif
253 {
254 error ("OBJECT IDENTIFIER length equal to zero");
255 XSRETURN_UNDEF;
256 }
257
258 U8 *end = cur + length;
259 U32 w = getb ();
260
261 //TODO: leading_dots
262
263 RETVAL = newSVpvf (".%d.%d", (int)w / 40, (int)w % 40);
264
265 while (cur < end)
266 {
267 w = getb ();
268 sv_catpvf (RETVAL, ".%u", (unsigned int)w);
269 }
270}
271 OUTPUT:
272 RETVAL
273 605
274SV * 606SV *
607_process_object_identifier (BUFOBJ self, ...)
608 CODE:
609 RETVAL = process_object_identifier_sv ();
610 OUTPUT: RETVAL
611
612SV *
275_process_octet_string (SV *self, ...) 613_process_octet_string (BUFOBJ self, ...)
276 ALIAS: 614 ALIAS:
277 _process_opaque = 0 615 _process_opaque = 0
278 CODE: 616 CODE:
279{ 617 RETVAL = process_octet_string_sv ();
280 U32 length = process_length (); 618 OUTPUT: RETVAL
281
282 U8 *data = getn (length, 0);
283 if (!data)
284 {
285 error ("OCTET STRING too long");
286 XSRETURN_UNDEF;
287 }
288
289 RETVAL = newSVpvn (data, length);
290}
291 OUTPUT:
292 RETVAL
293 619
294SV * 620SV *
295_process_ipaddress (SV *self, ...) 621_process_ipaddress (BUFOBJ self, ...)
296 CODE: 622 CODE:
297{ 623{
298 U32 length = process_length (); 624 U32 length = process_length ();
299 if (length != 4) 625 if (length != 4)
300 { 626 {
301 error ("IP ADDRESS length not four"); 627 error ("IP ADDRESS length not four");
302 XSRETURN_UNDEF; 628 XSRETURN_UNDEF;
303 } 629 }
304 630
305 U8 *data = getn (4, "\x00\x00\x00\x00"); 631 U8 *data = getn (4, "\x00\x00\x00\x00");
306 RETVAL = newSVpvf ("%d.%d.%d.%d", data [0], data [1], data [2], data [3]); 632 RETVAL = newSVpvf ("%d.%d.%d.%d", data [0], data [1], data [2], data [3]);
307} 633}
308 OUTPUT: 634 OUTPUT: RETVAL
635
636SV *
637process (BUFOBJ self, SV *expected = &PL_sv_undef, SV *found = 0)
638 CODE:
639{
640 int type;
641
642 RETVAL = process_sv (&type);
643
644 if (found)
645 sv_setiv (found, type);
646
647 if (SvOK (expected) && type != SvIV (expected))
648 error ("Expected a different type than found");
649}
650 OUTPUT: RETVAL
651
652MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::PDU
653
654SV *
655_process_var_bind_list (BUFOBJ self)
656 CODE:
657{
658 if (get8 () != (ASN_SEQUENCE | ASN_CONSTRUCTED))
659 error ("SEQUENCE expected at beginning of VarBindList");
660
661 int seqlen = process_length ();
662 U8 *end = cur + seqlen;
663
664 HV *list = newHV ();
665 AV *names = newAV ();
666 HV *types = newHV ();
667
668 hv_store ((HV *)cur_bufobj, "_var_bind_list" , sizeof ("_var_bind_list" ) - 1, newRV_noinc ((SV *)list ), 0);
669 hv_store ((HV *)cur_bufobj, "_var_bind_names", sizeof ("_var_bind_names") - 1, newRV_noinc ((SV *)names), 0);
670 hv_store ((HV *)cur_bufobj, "_var_bind_types", sizeof ("_var_bind_types") - 1, newRV_noinc ((SV *)types), 0);
671
672 while (cur < end && !errflag)
673 {
674 // SEQUENCE ObjectName ObjectSyntax
675 if (get8 () != (ASN_SEQUENCE | ASN_CONSTRUCTED))
676 error ("SEQUENCE expected at beginning of VarBind");
677 process_length ();
678
679 if (get8 () != ASN_OBJECT_IDENTIFIER)
680 error ("OBJECT IDENTIFIER expected at beginning of VarBind");
681 int type, oidlen;
682 SV *oid = process_object_identifier_sv ();
683 SV *val = process_sv (&type);
684
685 hv_store_ent (types, oid, newSViv (type), 0);
686 hv_store_ent (list , oid, val, 0);
687 av_push (names, oid);
688 }
689
690 // sigh - great design to do it here
691 SV *pdu_type = *hv_fetch ((HV *)cur_bufobj, "_pdu_type" , sizeof ("_pdu_type" ) - 1, 1);
692
693 if (SvIV (pdu_type) == 0xa8) // REPORT
694 {
695 PUSHMARK (SP);
696 XPUSHs (msg);
697 PUTBACK;
698 call_method ("_report_pdu_error", G_VOID | G_DISCARD);
699 SPAGAIN;
700 XSRETURN_EMPTY;
701 }
702
703 RETVAL = newRV_inc ((SV *)list);
704}
705 OUTPUT: RETVAL
706
707MODULE = Net::SNMP::XS PACKAGE = Net::SNMP
708
709void
710oid_base_match (SV *base_, SV *oid_)
711 PROTOTYPE: $$
712 ALIAS:
713 oid_context_match = 0
714 PPCODE:
715{
716 if (!SvOK (base_) || !SvOK (oid_))
717 XSRETURN_NO;
718
719 STRLEN blen, olen;
720 char *base = SvPVbyte (base_, blen);
721 char *oid = SvPVbyte (oid_ , olen);
722
723 blen -= *base == '.'; base += *base == '.';
724 olen -= *base == '.'; oid += *oid == '.';
725
726 if (olen < blen)
727 XSRETURN_NO;
728
729 if (memcmp (base, oid, blen))
730 XSRETURN_NO;
731
732 if (oid [blen] && oid [blen] != '.')
733 XSRETURN_NO;
734
735 XSRETURN_YES;
736}
737
738#if HAVE_VERSIONSORT
739
740void
741oid_lex_sort (...)
742 PROTOTYPE: @
743 PPCODE:
744{
745 // make sure SvPVX is valid
746 int i;
747 for (i = items; i--; )
748 {
749 SV *sv = ST (i);
750
751 if (SvTYPE (sv) < SVt_PV || SvTYPE (sv) == SVt_PVAV && SvTYPE (sv) == SVt_PVHV)
752 SvPV_force_nolen (sv);
753 }
754
755 qsort (&ST (0), items, sizeof (SV *), oid_lex_cmp);
756
757 EXTEND (SP, items);
758 // we cheat somewhat by not returning copies here
759 for (i = 0; i < items; ++i)
760 PUSHs (sv_2mortal (SvREFCNT_inc (ST (i))));
761}
762
763int
764_index_cmp (const char *a, const char *b)
765 PROTOTYPE: $$
766 CODE:
767 RETVAL = strverscmp (a, b);
309 RETVAL 768 OUTPUT: RETVAL
310 769
770#endif
771

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines