Usermode/libc - Cleanup of source
[tpg/acess2.git] / Usermode / Libraries / libc.so_src / stdio.c
1 /*\r
2  * AcessOS Basic C Library\r
3  * stdio.c\r
4  */\r
5 #include "config.h"\r
6 #include <acess/sys.h>\r
7 #include <stdlib.h>\r
8 #include <stdio.h>\r
9 #include <string.h>\r
10 #include "lib.h"\r
11 #include "stdio_int.h"\r
12 \r
13 #define WRITE_STR(_fd, _str)    write(_fd, _str, sizeof(_str))\r
14 \r
15 #define DEBUG_BUILD     0\r
16 \r
17 // === CONSTANTS ===\r
18 #define _stdin  0\r
19 #define _stdout 1\r
20 \r
21 // === PROTOTYPES ===\r
22 EXPORT void     itoa(char *buf, uint64_t num, size_t base, int minLength, char pad, int bSigned);\r
23 struct sFILE    *get_file_struct();\r
24 \r
25 // === GLOBALS ===\r
26 struct sFILE    _iob[STDIO_MAX_STREAMS];        // IO Buffer\r
27 struct sFILE    *stdin; // Standard Input\r
28 struct sFILE    *stdout;        // Standard Output\r
29 struct sFILE    *stderr;        // Standard Error\r
30 ///\note Initialised in SoMain\r
31 \r
32 // === CODE ===\r
33 int _fopen_modetoflags(const char *mode)\r
34 {\r
35         int flags = 0;\r
36         \r
37         // Get main mode\r
38         switch(*mode)\r
39         {\r
40         case 'r':       flags = FILE_FLAG_MODE_READ;    break;\r
41         case 'w':       flags = FILE_FLAG_MODE_WRITE;   break;\r
42         case 'a':       flags = FILE_FLAG_MODE_APPEND;  break;\r
43         case 'x':       flags = FILE_FLAG_MODE_EXEC;    break;  // Acess addon\r
44         default:\r
45                 return -1;\r
46         }\r
47         mode ++;\r
48 \r
49         // Get Modifiers\r
50         for( ; *mode; mode ++ )\r
51         {\r
52                 switch(*mode)\r
53                 {\r
54                 case 'b':       flags |= FILE_FLAG_M_BINARY;    break;\r
55                 case '+':       flags |= FILE_FLAG_M_EXT;       break;\r
56                 default:\r
57                         return -1;\r
58                 }\r
59         }\r
60         \r
61         return flags;\r
62 }\r
63 \r
64 /**\r
65  * \fn FILE *freopen(char *file, char *mode, FILE *fp)\r
66  */\r
67 EXPORT FILE *freopen(const char *file, const char *mode, FILE *fp)\r
68 {\r
69          int    openFlags = 0;\r
70         \r
71         // Sanity Check Arguments\r
72         if(!fp || !file || !mode)       return NULL;\r
73         \r
74         if(fp->FD != -1) {\r
75                 fflush(fp);\r
76         }\r
77 \r
78         // Get stdio flags\r
79         fp->Flags = _fopen_modetoflags(mode);\r
80         if(fp->Flags == -1)\r
81                 return NULL;\r
82         \r
83         // Get Open Flags\r
84         switch(fp->Flags & FILE_FLAG_MODE_MASK)\r
85         {\r
86         // Read\r
87         case FILE_FLAG_MODE_READ:\r
88                 openFlags = OPENFLAG_READ;\r
89                 if(fp->Flags & FILE_FLAG_M_EXT)\r
90                         openFlags |= OPENFLAG_WRITE;\r
91                 break;\r
92         // Write\r
93         case FILE_FLAG_MODE_WRITE:\r
94                 openFlags = OPENFLAG_WRITE;\r
95                 if(fp->Flags & FILE_FLAG_M_EXT)\r
96                         openFlags |= OPENFLAG_READ;\r
97                 break;\r
98         // Execute\r
99         case FILE_FLAG_MODE_APPEND:\r
100                 openFlags = OPENFLAG_APPEND;\r
101                 if(fp->Flags & FILE_FLAG_M_EXT)\r
102                         openFlags |= OPENFLAG_READ;\r
103                 break;\r
104         // Execute\r
105         case FILE_FLAG_MODE_EXEC:\r
106                 openFlags = OPENFLAG_EXEC;\r
107                 break;\r
108         }\r
109 \r
110         //Open File\r
111         if(fp->FD != -1)\r
112                 fp->FD = reopen(fp->FD, file, openFlags);\r
113         else\r
114                 fp->FD = open(file, openFlags);\r
115         if(fp->FD == -1) {\r
116                 fp->Flags = 0;\r
117                 return NULL;\r
118         }\r
119         \r
120         if( (fp->Flags & FILE_FLAG_MODE_MASK) == FILE_FLAG_MODE_APPEND ) {\r
121                 seek(fp->FD, 0, SEEK_END);      //SEEK_END\r
122         }\r
123         \r
124         return fp;\r
125 }\r
126 /**\r
127  \fn FILE *fopen(const char *file, const char *mode)\r
128  \brief Opens a file and returns the pointer\r
129  \param file    String - Filename to open\r
130  \param mode    Mode to open in\r
131 */\r
132 EXPORT FILE *fopen(const char *file, const char *mode)\r
133 {\r
134         FILE    *retFile;\r
135         \r
136         // Sanity Check Arguments\r
137         if(!file || !mode)      return NULL;\r
138         \r
139         // Create Return Structure\r
140         retFile = get_file_struct();\r
141         \r
142         return freopen(file, mode, retFile);\r
143 }\r
144 \r
145 EXPORT FILE *fmemopen(void *buffer, size_t length, const char *mode)\r
146 {\r
147         FILE    *ret;\r
148         \r
149         if( !buffer || !mode )  return NULL;\r
150         \r
151         ret = get_file_struct();\r
152         \r
153         ret->FD = -2;\r
154         ret->Flags = _fopen_modetoflags(mode);\r
155         if(ret->Flags == -1) {\r
156                 ret->Flags = 0;\r
157                 return NULL;\r
158         }\r
159         \r
160         ret->Buffer = buffer;\r
161         ret->BufferStart = 0;\r
162         ret->BufferSize = length;\r
163         \r
164         return ret;\r
165 }\r
166 \r
167 EXPORT int fclose(FILE *fp)\r
168 {\r
169         fflush(fp);\r
170         if( fp->FD != -1 ) {\r
171                 close(fp->FD);\r
172         }\r
173         fp->Flags = 0;\r
174         fp->FD = -1;\r
175         return 0;\r
176 }\r
177 \r
178 EXPORT void fflush(FILE *fp)\r
179 {\r
180         if( !fp || fp->FD == -1 )\r
181                 return ;\r
182         \r
183         if( !(fp->Flags & FILE_FLAG_DIRTY) )\r
184                 return ;\r
185         \r
186         // Nothing to do for memory files\r
187         if( fp->FD == -2 )\r
188                 return ;\r
189 }\r
190 \r
191 EXPORT void clearerr(FILE *fp)\r
192 {\r
193         if( !fp || fp->FD == -1 )\r
194                 return ;\r
195         \r
196         // TODO: Impliment clearerr()\r
197 }\r
198 \r
199 EXPORT int feof(FILE *fp)\r
200 {\r
201         if( !fp || fp->FD == -1 )\r
202                 return 0;\r
203         return !!(fp->Flags & FILE_FLAG_EOF);\r
204 }\r
205 \r
206 EXPORT int ferror(FILE *fp)\r
207 {\r
208         if( !fp || fp->FD == -1 )\r
209                 return 0;\r
210         return 0;\r
211 }\r
212 EXPORT int fileno(FILE *stream)\r
213 {\r
214         return stream->FD;\r
215 }\r
216 \r
217 EXPORT off_t ftell(FILE *fp)\r
218 {\r
219         if(!fp || fp->FD == -1) return -1;\r
220 \r
221         if( fp->FD == -2 )\r
222                 return fp->Pos; \r
223         else\r
224                 return tell(fp->FD);\r
225 }\r
226 \r
227 EXPORT int fseek(FILE *fp, long int amt, int whence)\r
228 {\r
229         if(!fp || fp->FD == -1) return -1;\r
230 \r
231         if( fp->FD == -2 ) {\r
232                 switch(whence)\r
233                 {\r
234                 case SEEK_CUR:\r
235                         fp->Pos += amt;\r
236                         break;\r
237                 case SEEK_SET:\r
238                         fp->Pos = amt;\r
239                         break;\r
240                 case SEEK_END:\r
241                         if( fp->BufferSize < (size_t)amt )\r
242                                 fp->Pos = 0;\r
243                         else\r
244                                 fp->Pos = fp->BufferSize - amt;\r
245                         break;\r
246                 }\r
247                 if(fp->Pos > (off_t)fp->BufferSize) {\r
248                         fp->Pos = fp->BufferSize;\r
249                         fp->Flags |= FILE_FLAG_EOF;\r
250                 }\r
251                 return 0;\r
252         }\r
253         else\r
254                 return seek(fp->FD, amt, whence);\r
255 }\r
256 \r
257 \r
258 /**\r
259  * \fn EXPORT int vfprintf(FILE *fp, const char *format, va_list args)\r
260  * \brief Print to a file from a variable argument list\r
261  */\r
262 EXPORT int vfprintf(FILE *fp, const char *format, va_list args)\r
263 {\r
264         va_list tmpList;\r
265          int    size;\r
266 \r
267         if(!fp || !format)      return -1;\r
268 \r
269         va_copy(tmpList, args);\r
270         \r
271         size = vsnprintf(NULL, 0, (char*)format, tmpList);\r
272         char    buf[size+1];\r
273         vsnprintf(buf, size+1, (char*)format, args);\r
274         \r
275         // Write to stream\r
276         fwrite(buf, size, 1, fp);\r
277         \r
278         // Return written byte count\r
279         return size;\r
280 }\r
281 \r
282 /**\r
283  * \fn int fprintf(FILE *fp, const char *format, ...)\r
284  * \brief Print a formatted string to a stream\r
285  */\r
286 EXPORT int fprintf(FILE *fp, const char *format, ...)\r
287 {\r
288         va_list args;\r
289          int    ret;\r
290         \r
291         // Get Size\r
292         va_start(args, format);\r
293         ret = vfprintf(fp, (char*)format, args);\r
294         va_end(args);\r
295         \r
296         return ret;\r
297 }\r
298 \r
299 /**\r
300  * \fn EXPORT size_t fwrite(void *ptr, size_t size, size_t num, FILE *fp)\r
301  * \brief Write to a stream\r
302  */\r
303 EXPORT size_t fwrite(void *ptr, size_t size, size_t num, FILE *fp)\r
304 {\r
305         size_t  ret;\r
306         \r
307         if(!fp || fp->FD == -1)\r
308                 return -1;\r
309 \r
310         if( fp->FD == -2 ) {\r
311                 size_t  avail = (fp->BufferSize - fp->Pos) / size;\r
312                 if( avail == 0 )\r
313                         fp->Flags |= FILE_FLAG_EOF;\r
314                 if( num > avail )       num = avail;\r
315                 size_t  bytes = num * size;\r
316                 memcpy((char*)fp->Buffer + fp->Pos, ptr, bytes);\r
317                 fp->Pos += bytes;\r
318                 ret = num;\r
319         }\r
320         else {  \r
321                 ret = write(fp->FD, ptr, size*num);\r
322                 ret /= size;\r
323         }\r
324         \r
325         return ret;\r
326 }\r
327 \r
328 /**\r
329  * \fn EXPORT size_t fread(void *ptr, size_t size, size_t num, FILE *fp)\r
330  * \brief Read from a stream\r
331  */\r
332 EXPORT size_t fread(void *ptr, size_t size, size_t num, FILE *fp)\r
333 {\r
334         size_t  ret;\r
335         \r
336         if(!fp || fp->FD == -1)\r
337                 return -1;\r
338 \r
339         if( fp->FD == -2 ) {\r
340                 size_t  avail = (fp->BufferSize - fp->Pos) / size;\r
341                 if( avail == 0 )\r
342                         fp->Flags |= FILE_FLAG_EOF;\r
343                 if( num > avail )       num = avail;\r
344                 size_t  bytes = num * size;\r
345                 memcpy(ptr, (char*)fp->Buffer + fp->Pos, bytes);\r
346                 fp->Pos += bytes;\r
347                 ret = num;\r
348         }\r
349         else {\r
350                 ret = read(fp->FD, ptr, size*num);\r
351                 ret /= size;\r
352         }\r
353                 \r
354         return ret;\r
355 }\r
356 \r
357 /**\r
358  * \fn EXPORT int fputc(int c, FILE *fp)\r
359  * \brief Write a single character to the stream\r
360  */\r
361 EXPORT int fputc(int c, FILE *fp)\r
362 {\r
363         return fwrite(&c, 1, 1, fp);\r
364 }\r
365 \r
366 EXPORT int putchar(int c)\r
367 {\r
368         c &= 0xFF;\r
369         return write(_stdout, &c, 1);\r
370 }\r
371 \r
372 /**\r
373  * \fn EXPORT int fgetc(FILE *fp)\r
374  * \brief Read a character from the stream\r
375  */\r
376 EXPORT int fgetc(FILE *fp)\r
377 {\r
378         char    ret = 0;\r
379         if( fread(&ret, 1, 1, fp) == (size_t)-1 )\r
380                 return -1;\r
381         return ret;\r
382 }\r
383 \r
384 EXPORT int getchar(void)\r
385 {\r
386         char    ret = 0;\r
387         if(read(_stdin, &ret, 1) != 1)  return -1;\r
388         return ret;\r
389 }\r
390 \r
391 // --- INTERNAL ---\r
392 /**\r
393  * \fn FILE *get_file_struct()\r
394  * \brief Returns a file descriptor structure\r
395  */\r
396 FILE *get_file_struct()\r
397 {\r
398          int    i;\r
399         for(i=0;i<STDIO_MAX_STREAMS;i++)\r
400         {\r
401                 if(_iob[i].Flags & FILE_FLAG_ALLOC)\r
402                         continue ;\r
403                 _iob[i].Flags |= FILE_FLAG_ALLOC;\r
404                 _iob[i].FD = -1;\r
405                 _iob[i].Pos = 0;\r
406                 return &_iob[i];\r
407         }\r
408         return NULL;\r
409 }\r
410 \r
411 EXPORT int puts(const char *str)\r
412 {\r
413          int    len;\r
414         \r
415         if(!str)        return 0;\r
416         len = strlen(str);\r
417         \r
418         len = write(_stdout, str, len);\r
419         write(_stdout, "\n", 1);\r
420         return len;\r
421 }\r
422 \r
423 EXPORT int vsprintf(char * __s, const char *__format, va_list __args)\r
424 {\r
425         return vsnprintf(__s, 0x7FFFFFFF, __format, __args);\r
426 }\r
427 \r
428 //sprintfv\r
429 /**\r
430  * \fn EXPORT void vsnprintf(char *buf, const char *format, va_list args)\r
431  * \brief Prints a formatted string to a buffer\r
432  * \param buf   Pointer - Destination Buffer\r
433  * \param format        String - Format String\r
434  * \param args  VarArgs List - Arguments\r
435  */\r
436 EXPORT int vsnprintf(char *buf, size_t __maxlen, const char *format, va_list args)\r
437 {\r
438         char    tmp[65];\r
439          int    c, minSize, precision, len;\r
440         size_t  pos = 0;\r
441         char    *p;\r
442         char    pad;\r
443         uint64_t        arg;\r
444          int    bLongLong, bPadLeft;\r
445 \r
446         void _addchar(char ch)\r
447         {\r
448                 if(buf && pos < __maxlen)       buf[pos] = ch;\r
449                 pos ++;\r
450         }\r
451 \r
452         tmp[32] = '\0';\r
453         \r
454         while((c = *format++) != 0)\r
455         {\r
456                 // Non-control character\r
457                 if (c != '%') {\r
458                         _addchar(c);\r
459                         continue;\r
460                 }\r
461                 \r
462                 // Control Character\r
463                 c = *format++;\r
464                 if(c == '%') {  // Literal %\r
465                         _addchar('%');\r
466                         continue;\r
467                 }\r
468                 \r
469                 bPadLeft = 0;\r
470                 bLongLong = 0;\r
471                 minSize = 0;\r
472                 precision = -1;\r
473                 pad = ' ';\r
474                 \r
475                 // Padding Character\r
476                 if(c == '0') {\r
477                         pad = '0';\r
478                         c = *format++;\r
479                 }\r
480                 // Padding length\r
481                 if( c == '*' ) {\r
482                         // Variable length\r
483                         minSize = va_arg(args, size_t);\r
484                         c = *format++;\r
485                 }\r
486                 else {\r
487                         if('1' <= c && c <= '9')\r
488                         {\r
489                                 minSize = 0;\r
490                                 while('0' <= c && c <= '9')\r
491                                 {\r
492                                         minSize *= 10;\r
493                                         minSize += c - '0';\r
494                                         c = *format++;\r
495                                 }\r
496                         }\r
497                 }\r
498 \r
499                 // Precision\r
500                 if(c == '.') {\r
501                         c = *format++;\r
502                         if(c == '*') {\r
503                                 precision = va_arg(args, size_t);\r
504                                 c = *format++;\r
505                         }\r
506                         else if('1' <= c && c <= '9')\r
507                         {\r
508                                 precision = 0;\r
509                                 while('0' <= c && c <= '9')\r
510                                 {\r
511                                         precision *= 10;\r
512                                         precision += c - '0';\r
513                                         c = *format++;\r
514                                 }\r
515                         }\r
516                 }\r
517         \r
518                 // Check for long long\r
519                 bLongLong = 0;\r
520                 if(c == 'l')\r
521                 {\r
522                         c = *format++;\r
523                         if(c == 'l') {\r
524                                 bLongLong = 1;\r
525                         }\r
526                 }\r
527                 \r
528                 // Just help things along later\r
529                 p = tmp;\r
530                 \r
531                 // Get Type\r
532                 switch( c )\r
533                 {\r
534                 // Signed Integer\r
535                 case 'd':       case 'i':\r
536                         // Get Argument\r
537                         if(bLongLong)   arg = va_arg(args, int64_t);\r
538                         else                    arg = va_arg(args, int32_t);\r
539                         itoa(tmp, arg, 10, minSize, pad, 1);\r
540                         precision = -1;\r
541                         goto sprintf_puts;\r
542                 \r
543                 // Unsigned Integer\r
544                 case 'u':\r
545                         // Get Argument\r
546                         if(bLongLong)   arg = va_arg(args, uint64_t);\r
547                         else                    arg = va_arg(args, uint32_t);\r
548                         itoa(tmp, arg, 10, minSize, pad, 0);\r
549                         precision = -1;\r
550                         goto sprintf_puts;\r
551                 \r
552                 // Pointer\r
553                 case 'p':\r
554                         _addchar('*');\r
555                         _addchar('0');\r
556                         _addchar('x');\r
557                         arg = va_arg(args, intptr_t);\r
558                         itoa(tmp, arg, 16, minSize, pad, 0);\r
559                         precision = -1;\r
560                         goto sprintf_puts;\r
561                 // Unsigned Hexadecimal\r
562                 case 'x':\r
563                         if(bLongLong)   arg = va_arg(args, uint64_t);\r
564                         else                    arg = va_arg(args, uint32_t);\r
565                         itoa(tmp, arg, 16, minSize, pad, 0);\r
566                         precision = -1;\r
567                         goto sprintf_puts;\r
568                 \r
569                 // Unsigned Octal\r
570                 case 'o':\r
571                         if(bLongLong)   arg = va_arg(args, uint64_t);\r
572                         else                    arg = va_arg(args, uint32_t);\r
573                         itoa(tmp, arg, 8, minSize, pad, 0);\r
574                         precision = -1;\r
575                         goto sprintf_puts;\r
576                 \r
577                 // Unsigned binary\r
578                 case 'b':\r
579                         if(bLongLong)   arg = va_arg(args, uint64_t);\r
580                         else                    arg = va_arg(args, uint32_t);\r
581                         itoa(tmp, arg, 2, minSize, pad, 0);\r
582                         precision = -1;\r
583                         goto sprintf_puts;\r
584 \r
585                 // String\r
586                 case 's':\r
587                         p = va_arg(args, char*);\r
588                 sprintf_puts:\r
589                         if(!p)  p = "(null)";\r
590                         //_SysDebug("vsnprintf: p = '%s'", p);\r
591                         if(precision >= 0)\r
592                                 len = strnlen(p, precision);\r
593                         else\r
594                                 len = strlen(p);\r
595                         if(bPadLeft)    while(minSize > len++)  _addchar(pad);\r
596                         while( *p ) {\r
597                                 if(precision >= 0 && precision -- == 0)\r
598                                         break;\r
599                                 _addchar(*p++);\r
600                         }\r
601                         if(!bPadLeft)   while(minSize > len++)  _addchar(pad);\r
602                         break;\r
603 \r
604                 // Unknown, just treat it as a character\r
605                 default:\r
606                         arg = va_arg(args, uint32_t);\r
607                         _addchar(arg);\r
608                         break;\r
609                 }\r
610         }\r
611         _addchar('\0');\r
612         pos --;\r
613         \r
614         //_SysDebug("vsnprintf: buf = '%s'", buf);\r
615         \r
616         return pos;\r
617 }\r
618 \r
619 const char cUCDIGITS[] = "0123456789ABCDEF";\r
620 /**\r
621  * \fn static void itoa(char *buf, uint64_t num, int base, int minLength, char pad, int bSigned)\r
622  * \brief Convert an integer into a character string\r
623  * \param buf   Destination Buffer\r
624  * \param num   Number to convert\r
625  * \param base  Base-n number output\r
626  * \param minLength     Minimum length of output\r
627  * \param pad   Padding used to ensure minLength\r
628  * \param bSigned       Signed number output?\r
629  */\r
630 EXPORT void itoa(char *buf, uint64_t num, size_t base, int minLength, char pad, int bSigned)\r
631 {\r
632         char    tmpBuf[64];\r
633          int    pos=0, i;\r
634 \r
635         if(!buf)        return;\r
636         if(base > 16 || base < 2) {\r
637                 buf[0] = 0;\r
638                 return;\r
639         }\r
640         \r
641         if(bSigned && (int64_t)num < 0)\r
642         {\r
643                 num = -num;\r
644                 bSigned = 1;\r
645         } else\r
646                 bSigned = 0;\r
647         \r
648         // Encode into reversed string\r
649         while(num > base-1) {\r
650                 tmpBuf[pos++] = cUCDIGITS[ num % base ];\r
651                 num = (uint64_t) num / (uint64_t)base;          // Shift {number} right 1 digit\r
652         }\r
653 \r
654         tmpBuf[pos++] = cUCDIGITS[ num % base ];                // Last digit of {number}\r
655         if(bSigned)     tmpBuf[pos++] = '-';    // Append sign symbol if needed\r
656         \r
657         i = 0;\r
658         minLength -= pos;\r
659         while(minLength-- > 0)  buf[i++] = pad;\r
660         while(pos-- > 0)                buf[i++] = tmpBuf[pos]; // Reverse the order of characters\r
661         buf[i] = 0;\r
662 }\r
663 \r
664 /**\r
665  * \fn EXPORT int printf(const char *format, ...)\r
666  * \brief Print a string to stdout\r
667  */\r
668 EXPORT int printf(const char *format, ...)\r
669 {\r
670          int    size;\r
671         va_list args;\r
672         \r
673         // Get final size\r
674         va_start(args, format);\r
675         size = vsnprintf(NULL, 0, (char*)format, args);\r
676         va_end(args);\r
677         char buf[size+1];\r
678         // Fill Buffer\r
679         va_start(args, format);\r
680         vsnprintf(buf, size+1, (char*)format, args);\r
681         va_end(args);\r
682         \r
683         // Send to stdout\r
684         write(_stdout, buf, size+1);\r
685         \r
686         // Free buffer\r
687         free(buf);\r
688         // Return\r
689         return size;\r
690 }\r
691 \r
692 /**\r
693  * \fn EXPORT int sprintf(const char *buf, char *format, ...)\r
694  * \brief Print a formatted string to a buffer\r
695  */\r
696 EXPORT int sprintf(char *buf, const char *format, ...)\r
697 {\r
698          int    ret;\r
699         va_list args;\r
700         va_start(args, format);\r
701         ret = vsprintf((char*)buf, (char*)format, args);\r
702         va_end(args);\r
703         return ret;\r
704 }\r
705 \r
706 /**\r
707  * \fn EXPORT int snprintf(const char *buf, size_t maxlen, char *format, ...)\r
708  * \brief Print a formatted string to a buffer\r
709  */\r
710 EXPORT int snprintf(char *buf, size_t maxlen, const char *format, ...)\r
711 {\r
712          int    ret;\r
713         va_list args;\r
714         va_start(args, format);\r
715         ret = vsnprintf((char*)buf, maxlen, (char*)format, args);\r
716         va_end(args);\r
717         return ret;\r
718 }\r

UCC git Repository :: git.ucc.asn.au