Implement strstr.
[monolithium.git] / kernel / src / common.c
1 /*
2  * common.c
3  *
4  * Copyright (C) 2016 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <common.h>
21 #include <heap.h>
22 #include <video.h>
23 #include <thread.h>
24 #include <timer.h>
25
26 static dword_t terminal_row = 0, terminal_col = 0;
27 static byte_t terminal_color = 0x07;
28
29 bool_t video_initialized = FALSE;
30
31 void enter_critical(critical_t *critical)
32 {
33     *critical = 0;
34     if (check_ints()) *critical |= (1 << 0);
35     if (scheduler_enabled) *critical |= (1 << 1);
36
37     disable_ints();
38     scheduler_enabled = FALSE;
39 }
40
41 void leave_critical(critical_t *critical)
42 {
43     if (*critical & (1 << 0)) enable_ints();
44     if (*critical & (1 << 1)) scheduler_enabled = TRUE;
45 }
46
47 int strlen(const char *str)
48 {
49     int ret = 0;
50     while (str[ret] != 0) ret++;
51     return ret;
52 }
53
54 void strrev(char *str)
55 {
56     int i, len = strlen(str);
57     for (i = 0; i < len / 2; i++)
58     {
59         int t = str[i];
60         str[i] = str[len - i - 1];
61         str[len - i - 1] = t;
62     }
63 }
64
65 const char *get_error_string(dword_t err_num)
66 {
67     static const char *error_strings[] = {
68         "ERR_SUCCESS",
69         "ERR_NOTFOUND",
70         "ERR_FORBIDDEN",
71         "ERR_INVALID",
72         "ERR_EXISTS",
73         "ERR_NOMEMORY",
74         "ERR_HARDWARE",
75         "ERR_BUSY",
76         "ERR_NOMEDIA",
77         "ERR_NOTRESP",
78         "ERR_WRITEPROT",
79         "ERR_NOSYSCALL",
80         "ERR_TIMEOUT",
81         "ERR_BADPTR",
82         "ERR_CANCELED",
83         "ERR_ISDIR",
84         "ERR_ISNOTDIR",
85         "ERR_DISKFULL",
86         "ERR_MEDIACHG"
87     };
88
89     if (err_num == 0)
90     {
91         return error_strings[0];
92     }
93     else if (err_num >= 0xE0000000)
94     {
95         if (err_num < MAX_ERR) return error_strings[err_num - 0xE0000000];
96     }
97
98     return NULL;
99 }
100
101 char *itoa(int value, char *str, int base)
102 {
103     int cnt = 0;
104     const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
105     bool_t append_sign = FALSE;
106
107     if (base < 2 || base > 36) return NULL;
108
109     if (value == 0)
110     {
111         str[0] = '0';
112         str[1] = 0;
113         return str;
114     }
115
116     if (value < 0 && base == 10)
117     {
118         value = -value;
119         append_sign = TRUE;
120     }
121
122     dword_t u_value = (dword_t) value;
123
124     while (u_value > 0)
125     {
126         str[cnt++] = digits[u_value % base];
127         u_value /= base;
128     }
129
130     if (append_sign) str[cnt++] = '-';
131     str[cnt] = 0;
132     strrev(str);
133
134     return str;
135 }
136
137 int atoi(char *str)
138 {
139     int i, len = strlen(str);
140     int ret = 0, z = 1;
141     bool_t negative = (str[0] == '-');
142
143     if (negative) str[0] = '0';
144
145     for (i = len - 1; i >= 0; i--)
146     {
147         if (str[i] >= '0' && str[i] <= '9')
148         {
149             int prev_ret = ret;
150             ret += (str[i] - '0') * z;
151             if (ret < prev_ret)
152             {
153                 ret = 2147483647;
154                 break;
155             }
156             z *= 10;
157         }
158         else return 0;
159     }
160     if (negative) return -ret;
161     else return ret;
162 }
163
164 int strtol(const char *str, char **endptr, int base)
165 {
166     int result = 0;
167     const char *ptr;
168     bool_t negative = FALSE;
169     const char *digits = "0123456789abcdefghijklmnopqrstuvxyz";
170     if (base < 2 || base > 36) return 0;
171
172     switch (*str)
173     {
174     case '-':
175         negative = TRUE;
176     case '+':
177         str++;
178         break;
179     }
180
181     for (ptr = str; *ptr; ptr++)
182     {
183         char *digit_ptr = strchr(digits, tolower(*ptr));
184         if (digit_ptr == NULL) break;
185
186         int digit = (int)(digit_ptr - digits);
187         if (digit >= base) break;
188
189         result *= base;
190         result += digit;
191     }
192
193     if (negative) result = -result;
194     if (endptr) *endptr = (char*)ptr;
195     return result;
196 }
197
198 dword_t strtoul(const char *str, char **endptr, int base)
199 {
200     dword_t result = 0;
201     const char *ptr;
202     const char *digits = "0123456789abcdefghijklmnopqrstuvxyz";
203     if (base < 2 || base > 36) return 0;
204
205     for (ptr = str; *ptr; ptr++)
206     {
207         char *digit_ptr = strchr(digits, tolower(*ptr));
208         if (digit_ptr == NULL) break;
209
210         int digit = (int)(digit_ptr - digits);
211         if (digit >= base || result > (DWORD_MAX / base)) break;
212
213         result *= base;
214         result += digit;
215     }
216
217     if (endptr) *endptr = (char*)ptr;
218     return result;
219 }
220
221 qword_t strtoull(const char *str, char **endptr, int base)
222 {
223     qword_t result = 0;
224     const char *ptr;
225     const char *digits = "0123456789abcdefghijklmnopqrstuvxyz";
226     if (base < 2 || base > 36) return 0;
227
228     for (ptr = str; *ptr; ptr++)
229     {
230         char *digit_ptr = strchr(digits, tolower(*ptr));
231         if (digit_ptr == NULL) break;
232
233         int digit = (int)(digit_ptr - digits);
234         if (digit >= base) break;
235         if (result > (QWORD_MAX / base)) break;
236
237         result *= (qword_t)base;
238         result += (qword_t)digit;
239     }
240
241     if (endptr) *endptr = (char*)ptr;
242     return result;
243 }
244
245 void memset(void *ptr, byte_t value, dword_t size)
246 {
247     dword_t *p = ptr;
248     dword_t tile = value | (value << 8) | (value << 16) | (value << 24);
249
250     while (size >= sizeof(dword_t))
251     {
252         *p++ = tile;
253         size -= sizeof(dword_t);
254     }
255
256     byte_t *p_byte = (byte_t*)p;
257     while (size--) *p_byte++ = value;
258 }
259
260 void memcpy(void *destination, const void *source, dword_t size)
261 {
262     byte_t *src_byte = (byte_t*)source;
263     byte_t *dest_byte = (byte_t*)destination;
264     if (!size) return;
265
266     if (size >> 2)
267     {
268         asm volatile ("cld\n"
269                       "rep movsd\n"
270                       : "=D"(dest_byte), "=S"(src_byte)
271                       : "D"(destination), "S"(source), "c"(size >> 2)
272                       : "cc", "memory");
273     }
274
275     switch (size & 3)
276     {
277     case 3:
278         *dest_byte++ = *src_byte++;
279     case 2:
280         *dest_byte++ = *src_byte++;
281     case 1:
282         *dest_byte++ = *src_byte++;
283     }
284 }
285
286 void memmove(void *destination, const void *source, dword_t size)
287 {
288     if (!size) return;
289
290     if (destination < source)
291     {
292         byte_t *src_byte = (byte_t*)source;
293         byte_t *dest_byte = (byte_t*)destination;
294
295         if (size >> 2)
296         {
297             asm volatile ("cld\n"
298                           "rep movsd\n"
299                           : "=D"(dest_byte), "=S"(src_byte)
300                           : "D"(destination), "S"(source), "c"(size >> 2)
301                           : "cc", "memory");
302         }
303
304         switch (size & 3)
305         {
306         case 3:
307             *dest_byte++ = *src_byte++;
308         case 2:
309             *dest_byte++ = *src_byte++;
310         case 1:
311             *dest_byte++ = *src_byte++;
312         }
313     }
314     else if (destination > source)
315     {
316         byte_t *src_byte = (byte_t*)source + size;
317         byte_t *dest_byte = (byte_t*)destination + size;
318
319         switch (size & 3)
320         {
321         case 3:
322             *--dest_byte = *--src_byte;
323         case 2:
324             *--dest_byte = *--src_byte;
325         case 1:
326             *--dest_byte = *--src_byte;
327         }
328
329         if (size >> 2)
330         {
331             asm volatile ("std\n"
332                           "rep movsd\n"
333                           :
334                           : "D"(dest_byte - 4), "S"(src_byte - 4), "c"(size >> 2)
335                           : "cc", "memory");
336         }
337     }
338 }
339
340 int memcmp(const void *mem1, const void *mem2, dword_t size)
341 {
342     const byte_t *p1 = (byte_t*)mem1, *p2 = (byte_t*)mem2;
343
344     while (size--)
345     {
346         if (*p1 < *p2) return -1;
347         else if (*p1 > *p2) return 1;
348
349         p1++;
350         p2++;
351     }
352
353     return 0;
354 }
355
356 char *strcpy(char *destination, const char *source)
357 {
358     while (*source != 0) *(destination++) = *(source++);
359     *destination = 0;
360     return destination;
361 }
362
363 int strcmp(const char *str1, const char *str2)
364 {
365     while (*str1 || *str2)
366     {
367         if (*str1 != *str2) break;
368         str1++;
369         str2++;
370     }
371
372     if (*str1 < *str2) return -1;
373     else if (*str1 > *str2) return 1;
374     else return 0;
375 }
376
377 int isprint(int c)
378 {
379     return c >= 0x20 && c <= 0x7E;
380 }
381
382 int isdigit(int c)
383 {
384     return c >= '0' && c <= '9';
385 }
386
387 int isxdigit(int c)
388 {
389     return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
390 }
391
392 int isspace(int c)
393 {
394     return strchr(" \t\n\v\f\r", c) != NULL;
395 }
396
397 char tolower(char c)
398 {
399     if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
400     return c;
401 }
402
403 char toupper(char c)
404 {
405     if (c >= 'a' && c <= 'z') c -= 'a' - 'A';
406     return c;
407 }
408
409 int stricmp(const char *str1, const char *str2)
410 {
411     while (*str1 || *str2)
412     {
413         if (tolower(*str1) != tolower(*str2)) break;
414         str1++;
415         str2++;
416     }
417     if (*str1 < *str2) return -1;
418     else if (*str1 > *str2) return 1;
419     else return 0;
420 }
421
422 int strncmp(const char *str1, const char *str2, int length)
423 {
424     int i;
425     for (i = 0; i < length; i++)
426     {
427         if (str1[i] < str2[i]) return -1;
428         else if (str1[i] > str2[i]) return 1;
429         if (str1[i] == 0 || str2[i] == 0) break;
430     }
431     return 0;
432 }
433
434 char *strstr(const char *haystack, const char *needle)
435 {
436     while (*haystack)
437     {
438         const char *s1 = haystack;
439         const char *s2 = needle;
440         bool_t found = TRUE;
441
442         while (*s2)
443         {
444             if (*s1++ != *s2++)
445             {
446                 found = FALSE;
447                 break;
448             }
449         }
450
451         if (found) return (char*)haystack;
452         haystack++;
453     }
454
455     return NULL;
456 }
457
458 char *strchr(const char *str, char c)
459 {
460     while ((*str != 0) && (*str != c)) str++;
461     if (*str == c) return (char*)str;
462     else return NULL;
463 }
464
465 char *strrchr(const char *str, char c)
466 {
467     char *ret = NULL;
468     while (*str != 0)
469     {
470         if (*str == c) ret = (char*)str;
471         str++;
472     }
473     return ret;
474 }
475
476 char *strcat(char *destination, const char *source)
477 {
478     strcpy(strchr(destination, 0), source);
479     return destination;
480 }
481
482 char *strncpy(char *destination, const char *source, dword_t num)
483 {
484     char *ptr = destination;
485     while ((num--) && (*source)) *(ptr++) = *(source++);
486     *ptr = 0;
487     return destination;
488 }
489
490 char *strncat(char *destination, const char *source, dword_t num)
491 {
492     char *ptr = strchr(destination, 0);
493     while ((num--) && (*source)) *(ptr++) = *(source++);
494     *ptr = 0;
495     return destination;
496 }
497
498 char *strdup(const char *source)
499 {
500     char *destination = (char*)malloc(strlen(source) + 1);
501     if (destination) strcpy(destination, source);
502     return destination;
503 }
504
505 void set_text_color(byte_t color)
506 {
507     terminal_color = color;
508 }
509
510 void clearscreen(void)
511 {
512     word_t *video_mem = (word_t*)TEXT_VIDEO_MEMORY;
513     memset(video_mem, 0x00, TEXT_HEIGHT * TEXT_WIDTH * sizeof(word_t));
514 }
515
516 static void scroll(void)
517 {
518     word_t *video_mem = (word_t*)(video_initialized ? VIDEO_MEMORY : TEXT_VIDEO_MEMORY);
519     memcpy(video_mem, &video_mem[TEXT_WIDTH], TEXT_WIDTH * (TEXT_HEIGHT - 1) * 2);
520     memset(&video_mem[(TEXT_HEIGHT - 1) * TEXT_WIDTH], 0, TEXT_WIDTH * 2);
521 }
522
523 void putchar(char character)
524 {
525     if (character == '\n')
526     {
527         if (terminal_row < (TEXT_HEIGHT - 1)) terminal_row++;
528         else scroll();
529
530         terminal_col = 0;
531     }
532     else if (character == '\t')
533     {
534         terminal_col++;
535         while(terminal_col % 8) terminal_col++;
536     }
537     else if (character == '\r')
538     {
539         terminal_col = 0;
540     }
541     else
542     {
543         word_t *video_mem = (word_t*)(video_initialized ? VIDEO_MEMORY : TEXT_VIDEO_MEMORY);
544         video_mem[terminal_row * TEXT_WIDTH + terminal_col] = character | (terminal_color << 8);
545         terminal_col++;
546     }
547
548     if (terminal_col == TEXT_WIDTH)
549     {
550         if (terminal_row < (TEXT_HEIGHT - 1)) terminal_row++;
551         else scroll();
552         terminal_col = 0;
553     }
554
555     if (video_initialized)
556     {
557         video_cursor_location_t params = {
558             .row = terminal_row,
559             .column = terminal_col
560         };
561
562         device_t *video_device = get_char_device("Video0");
563         if (video_device) device_ioctl_internal(video_device, IOCTL_VIDEO_SET_TEXT_CURSOR, &params, sizeof(params), NULL, 0);
564     }
565     else
566     {
567         dword_t index = terminal_row * 80 + terminal_col;
568         outportb(VGA_CRTC_INDEX, 0x0F);
569         outportb(VGA_CRTC_DATA, (byte_t) (index & 0xFF));
570         outportb(VGA_CRTC_INDEX, 0x0E);
571         outportb(VGA_CRTC_DATA, (byte_t) ((index >> 8) & 0xFF));
572     }
573 }
574
575 void puts(const char *string)
576 {
577     while (*string) putchar(*(string++));
578     putchar('\n');
579 }
580
581 int vsnprintf(char *output, dword_t count, const char *format, va_list args)
582 {
583     int ret = 0;
584     const char *ptr;
585     const char *valid_flags = "-+ #0";
586
587     for (ptr = format; *ptr != '\0'; ptr++)
588     {
589         if (*ptr != '%')
590         {
591             if (count <= 1) break;
592             output[ret++] = *ptr;
593             count--;
594             continue;
595         }
596
597         ptr++;
598
599         int i;
600         char *p;
601         char padding_char = ' ', sign = '\0';
602         int padding = 0;
603         bool_t alternative = FALSE;
604         bool_t left_justify = FALSE;
605         bool_t external_width = FALSE;
606         bool_t external_precision = FALSE;
607         int width = 0, precision = -1;
608
609         while (*ptr)
610         {
611             char *flag = strchr(valid_flags, *ptr);
612             if (flag == NULL) break;
613
614             ptr++;
615
616             switch (*flag)
617             {
618             case '+':
619                 sign = '+';
620                 break;
621
622             case '-':
623                 left_justify = TRUE;
624                 break;
625
626             case ' ':
627                 sign = ' ';
628                 break;
629
630             case '#':
631                 alternative = TRUE;
632                 break;
633
634             case '0':
635                 padding_char = '0';
636                 break;
637             }
638         }
639
640         UNUSED_PARAMETER(alternative); // TODO
641
642         if (*ptr == '*') external_width = TRUE;
643         else width = strtol(ptr, (char**)&ptr, 10);
644
645         if (*ptr == '.')
646         {
647             ptr++;
648             if (*ptr == '*') external_precision = TRUE;
649             else precision = strtol(ptr, (char**)&ptr, 10);
650         }
651
652         char specifier = *ptr;
653
654         switch (specifier)
655         {
656         case '%':
657             if (count <= 1) goto done;
658             output[ret++] = '%';
659             count--;
660             break;
661
662         case 'c':
663             if (external_width) width = *va_arg(args, int*);
664             padding = width - 1;
665
666             if (!left_justify)
667             {
668                 for (i = 0; i < padding; i++)
669                 {
670                     if (count <= 1) goto done;
671                     output[ret++] = padding_char;
672                     count--;
673                 }
674             }
675
676             if (count <= 1) goto done;
677             output[ret++] = va_arg(args, int);
678             count--;
679
680             if (left_justify)
681             {
682                 for (i = 0; i < padding; i++)
683                 {
684                     if (count <= 1) goto done;
685                     output[ret++] = padding_char;
686                     count--;
687                 }
688             }
689
690             break;
691
692         case 'p':
693             padding_char = '0';
694             width = 8;
695             specifier = 'X';
696
697         case 'd':
698         case 'u':
699         case 'i':
700         case 'o':
701         case 'x':
702         case 'X':
703         {
704             char num_buffer[256];
705             int number = va_arg(args, int);
706             int number_base;
707
708             if (specifier == 'o') number_base = 8;
709             else if (tolower(specifier) == 'x') number_base = 16;
710             else number_base = 10;
711
712             if ((number > 0) && (sign != 0))
713             {
714                 itoa(number, &num_buffer[1], number_base);
715                 num_buffer[0] = sign;
716             }
717             else
718             {
719                 itoa(number, num_buffer, number_base);
720             }
721
722             if (external_width) width = *va_arg(args, int*);
723             if (external_precision) precision = *va_arg(args, int*);
724
725             if (strlen(num_buffer) < precision)
726             {
727                 int leading_zeros = precision - strlen(num_buffer);
728                 memmove(&num_buffer[leading_zeros], &num_buffer[0], leading_zeros);
729                 for (i = 0; i < leading_zeros; i++) num_buffer[i] = '0';
730             }
731
732             padding = (int)width - (int)strlen(num_buffer);
733
734             if (!left_justify)
735             {
736                 for (i = 0; i < padding; i++)
737                 {
738                     if (count <= 1) goto done;
739                     output[ret++] = padding_char;
740                     count--;
741                 }
742             }
743
744             for (p = num_buffer; *p != '\0'; p++)
745             {
746                 if (count <= 1) goto done;
747                 output[ret++] = *p;
748                 count--;
749             }
750
751             if (left_justify && width)
752             {
753                 for (i = 0; i < padding; i++)
754                 {
755                     if (count <= 1) goto done;
756                     output[ret++] = padding_char;
757                     count--;
758                 }
759             }
760
761             break;
762         }
763
764         case 's':
765         {
766             char *string = va_arg(args, char*);
767             if (external_width) width = *va_arg(args, int*);
768             if (external_precision) precision = *va_arg(args, int*);
769             padding = (int)width - (int)strlen(string);
770
771             if (!left_justify)
772             {
773                 for (i = 0; i < padding; i++)
774                 {
775                     if (count <= 1) goto done;
776                     output[ret++] = padding_char;
777                     count--;
778                 }
779             }
780
781             for (i = 0, p = string; *p != '\0' && (precision < 0 || i < precision); p++, i++)
782             {
783                 if (count <= 1) goto done;
784                 output[ret++] = *p;
785                 count--;
786             }
787
788             if (left_justify)
789             {
790                 for (i = 0; i < padding; i++)
791                 {
792                     if (count <= 1) goto done;
793                     output[ret++] = padding_char;
794                     count--;
795                 }
796             }
797
798             break;
799         }
800
801         case 'f':
802         case 'F':
803         case 'e':
804         case 'E':
805         case 'g':
806         case 'G':
807             // TODO: print decimal
808             UNUSED_PARAMETER(precision);
809             UNUSED_PARAMETER(external_precision);
810             break;
811
812         case 'n':
813         {
814             int *printed_chars = va_arg(args, int*);
815             *printed_chars = ret;
816             break;
817         }
818         }
819     }
820
821 done:
822     output[ret] = '\0';
823     return ret;
824 }
825
826 int snprintf(char *output, dword_t count, const char *format, ...)
827 {
828     va_list args;
829     va_start(args, format);
830     int ret = vsnprintf(output, count, format, args);
831     va_end(args);
832     return ret;
833 }
834
835 int vsprintf(char *output, const char *format, va_list args)
836 {
837     return vsnprintf(output, (dword_t)-1, format, args);
838 }
839
840 int vprintf(const char *format, va_list args)
841 {
842     char buffer[256];
843     int ret = vsnprintf(buffer, 256, format, args);
844
845     char *ptr = &buffer[0];
846     while (*ptr) putchar(*(ptr++));
847     return ret;
848 }
849
850 int sprintf(char *output, const char *format, ...)
851 {
852     va_list args;
853     va_start(args, format);
854     int ret = vsprintf(output, format, args);
855     va_end(args);
856     return ret;
857 }
858
859 int printf(const char *format, ...)
860 {
861     va_list args;
862     va_start(args, format);
863     int ret = vprintf(format, args);
864     va_end(args);
865     return ret;
866 }
867
868 char *strtok(char *str, const char *delimiters, char **endptr)
869 {
870     if (str == NULL) str = *endptr;
871     char *start = str;
872
873     while (*str != 0)
874     {
875         if (strchr(delimiters, *str) != NULL)
876         {
877             *str = 0;
878             *endptr = str + 1;
879             return start;
880         }
881
882         str++;
883     }
884
885     *endptr = str;
886     return start != str ? start : NULL;
887 }
888
889 int abs(int x)
890 {
891     return (x < 0) ? -x : x;
892 }
893
894 void qsort(void *base, dword_t nmemb, dword_t size, int (*compare)(const void*, const void*))
895 {
896     void *pivot = (void*)((uintptr_t)base + (nmemb / 2) * size);
897     void *temp = __builtin_alloca(size);
898     if (nmemb <= 1) return;
899
900     dword_t low = 0;
901     dword_t high = nmemb - 1;
902
903     while (TRUE)
904     {
905         while (compare((void*)((uintptr_t)base + low * size), pivot) < 0) low++;
906         while (compare((void*)((uintptr_t)base + high * size), pivot) > 0) high--;
907         if (low >= high) break;
908
909         memcpy(temp, (void*)((uintptr_t)base + low * size), size);
910         memcpy((void*)((uintptr_t)base + low * size), (void*)((uintptr_t)base + high * size), size);
911         memcpy((void*)((uintptr_t)base + high * size), temp, size);
912     }
913
914     qsort(base, high, size, compare);
915     qsort((void*)((dword_t)base + high * size), nmemb - high, size, compare);
916 }
917
918 void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compare)(const void*, const void*))
919 {
920     long low = 0;
921     long high = nmemb - 1;
922
923     while (low <= high)
924     {
925         long mid = low + (high - low) / 2;
926         void *current = (void*)((uintptr_t)base + mid * size);
927         int comp = compare(current, key);
928
929         if (comp < 0) low = mid + 1;
930         else if (comp > 0) high = mid - 1;
931         else return current;
932     }
933
934     return NULL;
935 }