2004-10-28 Casper S. Hornstrup <chorns@users.sourceforge.net>
[reactos.git] / reactos / tools / regtests.c
1 /*
2 * Generate a file with test registrations from a list
3 * of files in a directory.
4 * Casper S. Hornstrup <chorns@users.sourceforge.net>
5 */
6
7 #include <stdio.h>
8 #include <fcntl.h>
9 #include <sys/stat.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <ctype.h>
13
14 #ifdef WIN32
15 #include <io.h>
16 #include <dos.h>
17 #else
18 #include <sys/io.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <dirent.h>
22 #include <unistd.h>
23 #endif
24 #ifndef MAX_PATH
25 #define MAX_PATH 260
26 #endif
27 #ifndef WIN32
28 #define DIR_SEPARATOR_CHAR '/'
29 #define DIR_SEPARATOR_STRING "/"
30 #else
31 #define DIR_SEPARATOR_CHAR '\\'
32 #define DIR_SEPARATOR_STRING "\\"
33 #endif
34
35 static FILE *out;
36 static char *path;
37 static char *file;
38 static char *makefile;
39 static char *exestubfile;
40
41 static char*
42 convert_path(char* origpath)
43 {
44 char* newpath;
45 int i;
46
47 /* for no good reason, i'm having trouble getting gcc to link strdup */
48 //newpath = strdup(origpath);
49 newpath = malloc(strlen(origpath)+1);
50 strcpy(newpath, origpath);
51
52 i = 0;
53 while (newpath[i] != 0)
54 {
55 #ifndef WIN32
56 if (newpath[i] == '\\')
57 {
58 newpath[i] = '/';
59 }
60 #else
61 #ifdef WIN32
62 if (newpath[i] == '/')
63 {
64 newpath[i] = '\\';
65 }
66 #endif
67 #endif
68 i++;
69 }
70 return(newpath);
71 }
72
73 static void
74 write_line(char *line)
75 {
76 int n_out;
77 char buf[200];
78
79 memset(buf, 0, sizeof(buf));
80 strcpy(buf, line);
81 /* Terminate the line */
82 buf[strlen(buf)] = '\r';
83 buf[strlen(buf)] = '\n';
84
85 n_out = fwrite(&buf[0], 1, strlen(buf), out);
86 }
87
88 static void
89 change_extension(char *filenamebuffer,
90 char *filename,
91 char *newextension)
92 {
93 char *ptr;
94
95 if (newextension == NULL)
96 {
97 strcpy(filenamebuffer, filename);
98 return;
99 }
100
101 ptr = strrchr(filename, '.');
102 if (ptr != NULL)
103 {
104 strncpy(filenamebuffer, filename, ptr - filename);
105 filenamebuffer[ptr - filename] = 0;
106 strcat(filenamebuffer, newextension);
107 }
108 else
109 {
110 strcpy(filenamebuffer, filename);
111 strcat(filenamebuffer, newextension);
112 }
113 }
114
115 static void
116 get_test_name(char *filename,
117 char *testname)
118 {
119 int i;
120
121 strcpy(testname, filename);
122
123 i = strlen(testname);
124 while (i > 0 && testname[i] != '.')
125 {
126 i--;
127 }
128 if (i > 0)
129 {
130 testname[i] = 0;
131 }
132
133 /* Make a capital first letter and make all other letters lower case */
134 testname[0] = toupper(testname[0]);
135 if (!((testname[0] >= 'A' && testname[0] <= 'Z') ||
136 (testname[0] >= '0' && testname[0] <= '9')))
137 {
138 testname[0] = '_';
139 }
140 i = 1;
141 while (i < strlen(testname))
142 {
143 testname[i] = tolower(testname[i]);
144 if (!((testname[i] >= 'a' && testname[i] <= 'z') ||
145 (testname[i] >= '0' && testname[i] <= '9')))
146 {
147 testname[i] = '_';
148 }
149 i++;
150 }
151 }
152
153 /*
154 * filename - name of file to make registrations for
155 * type - type of registration (0 = prototype, 1 = call, 2 = makefile)
156 */
157 static void
158 register_test(char *filename,
159 int type)
160 {
161 char ext[100];
162 char testname[100];
163 char call[100];
164 char regtest[100];
165 char filenamebuffer[MAX_PATH];
166 int i;
167
168 i = strlen(filename);
169 while (i > 0 && filename[i] != '.')
170 {
171 i--;
172 }
173 if (i > 0)
174 {
175 memset(ext, 0, sizeof(ext));
176 strncpy(&ext[0], &filename[i], strlen(&filename[i]));
177
178 if ((strncmp(ext, ".c", 2) != 0) && (strncmp(ext, ".C", 2) != 0))
179 {
180 return;
181 }
182 }
183 else
184 {
185 return;
186 }
187
188 memset(testname, 0, sizeof(testname));
189 get_test_name(filename, testname);
190
191 if (type == 0)
192 {
193 sprintf(regtest, "extern int %sTest(int Command, char *Buffer);", testname);
194 write_line(regtest);
195 }
196 else if (type == 1)
197 {
198 sprintf(call, "%sTest", testname);
199 sprintf(regtest, " AddTest((TestRoutine)%s);", call);
200 write_line(regtest);
201 }
202 else if (type == 2)
203 {
204 change_extension(filenamebuffer, filename, ".o");
205 sprintf(regtest, "%s \\", filenamebuffer);
206 write_line(regtest);
207 }
208 }
209
210 #ifdef WIN32
211
212 /* Win32 version */
213
214 static void
215 make_file_list (int type)
216 {
217 struct _finddata_t f;
218 int findhandle;
219 char searchbuf[MAX_PATH];
220
221 strcpy(searchbuf, path);
222 strcat(searchbuf, "*.*");
223 findhandle =_findfirst(searchbuf, &f);
224 if (findhandle != -1)
225 {
226 do
227 {
228 if (f.attrib & _A_SUBDIR)
229 {
230 /* Skip subdirectories */
231 continue;
232 }
233
234 register_test(f.name, type);
235 }
236 while (_findnext(findhandle, &f) == 0);
237 _findclose(findhandle);
238 }
239 }
240
241 #else
242
243 /* Linux version */
244 static void
245 make_file_list (int type)
246 {
247 DIR *dirp;
248 struct dirent *entry;
249 struct stat stbuf;
250 char buf[MAX_PATH];
251
252 #ifdef HAVE_D_TYPE
253 dirp = opendir(path);
254 if (dirp != NULL)
255 {
256 while ((entry = readdir(dirp)) != NULL)
257 {
258 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
259 continue; // skip self and parent
260
261 if (entry->d_type == DT_REG) // normal file
262 {
263 // Check for an absolute path
264 if (path[0] == DIR_SEPARATOR_CHAR)
265 {
266 strcpy(buf, path);
267 strcat(buf, DIR_SEPARATOR_STRING);
268 strcat(buf, entry->d_name);
269 }
270 else
271 {
272 getcwd(buf, sizeof(buf));
273 strcat(buf, DIR_SEPARATOR_STRING);
274 strcat(buf, path);
275 strcat(buf, entry->d_name);
276 }
277
278 if (stat(buf, &stbuf) == -1)
279 {
280 printf("Can't access '%s' (%s)\n", buf, strerror(errno));
281 return;
282 }
283
284 if (S_ISDIR(stbuf.st_mode))
285 {
286 /* Skip subdirectories */
287 continue;
288 }
289
290 register_test(entry->d_name, type);
291 }
292 }
293 closedir(dirp);
294 }
295 else
296 {
297 printf("Can't open %s\n", path);
298 return;
299 }
300
301 #else
302
303 dirp = opendir(path);
304 if (dirp != NULL)
305 {
306 while ((entry = readdir(dirp)) != NULL)
307 {
308 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
309 continue; // skip self and parent
310
311 // Check for an absolute path
312 if (path[0] == DIR_SEPARATOR_CHAR)
313 {
314 strcpy(buf, path);
315 strcat(buf, DIR_SEPARATOR_STRING);
316 strcat(buf, entry->d_name);
317 }
318 else
319 {
320 getcwd(buf, sizeof(buf));
321 strcat(buf, DIR_SEPARATOR_STRING);
322 strcat(buf, path);
323 strcat(buf, entry->d_name);
324 }
325
326 if (stat(buf, &stbuf) == -1)
327 {
328 printf("Can't access '%s' (%s)\n", buf, strerror(errno));
329 return;
330 }
331
332 if (S_ISDIR(stbuf.st_mode))
333 {
334 /* Skip subdirectories */
335 continue;
336 }
337
338 register_test(entry->d_name, type);
339 }
340 closedir(dirp);
341 }
342 else
343 {
344 printf("Can't open %s\n", path);
345 return;
346 }
347
348 #endif
349 }
350
351 #endif
352
353 static int
354 is_file_changed(char *filename,
355 char *content)
356 {
357 FILE *file;
358 int size;
359 int n;
360 char *filecontent;
361
362 file = fopen(filename, "rb");
363 if (file == NULL)
364 {
365 return 1;
366 }
367
368 fseek(file, 0, SEEK_END);
369 size = ftell(file);
370 fseek(file, 0, SEEK_SET);
371 if (size <= 0)
372 {
373 fclose(file);
374 return 1;
375 }
376 filecontent = malloc(size);
377 if (filecontent == NULL)
378 {
379 fclose(file);
380 return 1;
381 }
382
383 n = fread(filecontent, 1, size, file);
384
385 if (n != strlen(content))
386 {
387 free(filecontent);
388 fclose(file);
389 return 1;
390 }
391
392 if (strcmp(content, filecontent) != 0)
393 {
394 free(filecontent);
395 fclose(file);
396 return 1;
397 }
398
399 free(filecontent);
400
401 fclose(file);
402
403 return 0;
404 }
405
406 static int
407 write_file_if_changed(char *filename,
408 char *content)
409 {
410 FILE *file;
411 int n;
412
413 if (is_file_changed(filename, content) == 0)
414 {
415 return 0;
416 }
417
418 file = fopen(filename, "wb");
419 if (file == NULL)
420 {
421 return 1;
422 }
423
424 n = fwrite(content, 1, strlen(content), file);
425
426 fclose(file);
427
428 return 0;
429 }
430
431 static char EXESTUB[] =
432 "/* This file is autogenerated. */\n"
433 "\n"
434 "#include <windows.h>\n"
435 "#include \"regtests.h\"\n"
436 "\n"
437 "void\n"
438 "ConsoleWrite(char *Buffer)\n"
439 "{\n"
440 " printf(Buffer);\n"
441 "}\n"
442 "\n"
443 "int\n"
444 "mainCRTStartup(HANDLE hInstance,\n"
445 " HANDLE hPrevInstance,\n"
446 " LPSTR lpszCmdParam,\n"
447 " int nCmdShow)\n"
448 "{\n"
449 " InitializeTests();\n"
450 " RegisterTests();\n"
451 " SetupOnce();\n"
452 " PerformTests(ConsoleWrite, NULL);\n"
453 " _ExitProcess(0);\n"
454 " return 0;\n"
455 "}\n";
456
457 static char STUBS_HEADER[] =
458 "/* This file is autogenerated. */\n"
459 "passthrough:\n"
460 " call _FrameworkGetHook@4\n"
461 " test %eax, %eax\n"
462 " je .return\n"
463 " jmp *%eax\n"
464 ".return:\n"
465 " /* This will most likely corrupt the stack */\n"
466 " ret\n"
467 "\n";
468
469 static char HOOKS_HEADER[] =
470 "/* This file is autogenerated. */\n"
471 "#include <windows.h>\n"
472 "#include \"regtests.h\"\n"
473 "\n"
474 "API_DESCRIPTION ExternalDependencies[] =\n"
475 "{\n";
476
477 static char HOOKS_FOOTER[] =
478 "};\n"
479 "\n"
480 "#define ExternalDependencyCount %d\n"
481 "ULONG MaxExternalDependency = ExternalDependencyCount - 1;\n";
482
483 static char HELP[] =
484 "REGTESTS path file makefile [-e exestubfile]\n"
485 "REGTESTS -s stublistfile stubsfile hooksfile\n"
486 "\n"
487 " path Path to files\n"
488 " file Registration file to create\n"
489 " makefile Makefile to create\n"
490 " exestubfile Optional stub for running tests in the build environment\n"
491 " stublistfile File with descriptions of stubs\n"
492 " stubsfile File with stubs to create\n"
493 " hooksfile File with hooks to create\n";
494
495 #define INPUT_BUFFER_SIZE 255
496
497 void
498 write_stubs_header(FILE * out)
499 {
500 fputs(STUBS_HEADER, out);
501 }
502
503 void
504 write_hooks_header(FILE * out)
505 {
506 fputs(HOOKS_HEADER, out);
507 }
508
509 void
510 write_hooks_footer(FILE *hooks_out, unsigned long nr_stubs)
511 {
512 fprintf(hooks_out, HOOKS_FOOTER, nr_stubs);
513 }
514
515 char *
516 get_symbolname(char *decoratedname)
517 {
518 char buf[300];
519
520 if (decoratedname[0] == '@')
521 return strdup(decoratedname);
522 strcpy(buf, "_");
523 strcat(buf, decoratedname);
524 return strdup(buf);
525 }
526
527 char *
528 get_undecorated_name(char *buf,
529 char *decoratedname)
530 {
531 int start = 0;
532 int end = 0;
533
534 while (start < strlen(decoratedname) && decoratedname[start] == '@')
535 {
536 start++;
537 }
538 strcpy(buf, &decoratedname[start]);
539 end = strlen(buf) - 1;
540 while (end > 0 && isdigit(buf[end]))
541 {
542 end--;
543 }
544 if (buf[end] == '@')
545 {
546 buf[end] = 0;
547 }
548 return buf;
549 }
550
551 char *
552 get_forwarded_export(char *forwardedexport)
553 {
554 char buf[300];
555
556 if (forwardedexport == NULL)
557 {
558 strcpy(buf, "NULL");
559 }
560 else
561 {
562 sprintf(buf, "\"%s\"", forwardedexport);
563 }
564 return strdup(buf);
565 }
566
567 void
568 write_stub(FILE *stubs_out, FILE *hooks_out, char *dllname,
569 char *decoratedname_and_forward, unsigned int stub_index)
570 {
571 char buf[300];
572 char *p;
573 char *decoratedname = NULL;
574 char *forwardedexport = NULL;
575 char *symbolname = NULL;
576
577 p = strtok(decoratedname_and_forward, "=");
578 if (p != NULL)
579 {
580 decoratedname = p;
581
582 p = strtok(NULL, "=");
583 forwardedexport = p;
584 }
585 else
586 {
587 decoratedname = decoratedname_and_forward;
588 forwardedexport = decoratedname_and_forward;
589 }
590
591 symbolname = get_symbolname(decoratedname);
592 fprintf(stubs_out, ".globl %s\n", symbolname);
593 fprintf(stubs_out, "%s:\n", symbolname);
594 free(symbolname);
595 fprintf(stubs_out, " pushl $%d\n", stub_index);
596 fprintf(stubs_out, " jmp passthrough\n");
597 fprintf(stubs_out, "\n");
598 forwardedexport = get_forwarded_export(forwardedexport);
599 fprintf(hooks_out, " {\"%s\", \"%s\", %s, NULL, NULL},\n",
600 dllname,
601 get_undecorated_name(buf, decoratedname),
602 forwardedexport);
603 free(forwardedexport);
604 }
605
606 void
607 create_stubs_and_hooks(
608 FILE *in,
609 FILE *stubs_out,
610 FILE *hooks_out)
611 {
612 char line[INPUT_BUFFER_SIZE];
613 char *s;
614 char *dllname;
615 char *decoratedname_and_forward;
616 int stub_index;
617
618 write_stubs_header(stubs_out);
619
620 write_hooks_header(hooks_out);
621
622 /*
623 * Scan the database. The database is a text file; each
624 * line is a record, which contains data for one stub.
625 * Each record has two columns:
626 *
627 * DLLNAME (e.g. ntdll.dll)
628 * DECORATED NAME (e.g. NtCreateProcess@32, @InterlockedIncrement@4 or printf)
629 */
630 stub_index = 0; /* First stub has index zero */
631
632 for (
633 ;
634 /* Go on until EOF or read zero bytes */
635 ((!feof(in)) && (fgets(line, sizeof line, in) != NULL));
636 /* Next stub index */
637 )
638 {
639 /*
640 * Remove, if present, the trailing LF.
641 */
642 if ((s = (char *) strchr(line,'\n')) != NULL)
643 {
644 *s = '\0';
645 }
646
647 /*
648 * Remove, if present, the trailing CR.
649 */
650 if ((s = (char *) strchr(line,'\r')) != NULL)
651 {
652 *s = '\0';
653 }
654
655 /*
656 * Skip comments (#) and empty lines.
657 */
658 s = & line[0];
659 if ((*s) != '#' && (*s) != '\0')
660 {
661 /* Extract the DLL name */
662 dllname = (char *) strtok(s, " \t");
663 if (dllname != NULL && strlen(dllname) > 0)
664 {
665 /*
666 * Extract the decorated function name and possibly forwarded export.
667 * Format:
668 * decoratedname=forwardedexport (no DLL name)
669 */
670 decoratedname_and_forward = (char *) strtok(NULL, " \t");
671 /* Extract the argument count */
672 write_stub(stubs_out, hooks_out, dllname, decoratedname_and_forward, stub_index);
673 stub_index++;
674 }
675 }
676 }
677
678 write_hooks_footer(hooks_out, stub_index);
679 }
680
681 int run_stubs(int argc,
682 char **argv)
683 {
684 FILE *in;
685 FILE *stubs_out;
686 FILE *hooks_out;
687
688 in = fopen(argv[2], "rb");
689 if (in == NULL)
690 {
691 perror("Failed to open stub description input file");
692 return 1;
693 }
694
695 stubs_out = fopen(argv[3], "wb");
696 if (stubs_out == NULL)
697 {
698 perror("Failed to open stubs output file");
699 return 1;
700 }
701
702 hooks_out = fopen(argv[4], "wb");
703 if (hooks_out == NULL)
704 {
705 perror("Failed to open hooks output file");
706 return 1;
707 }
708
709 create_stubs_and_hooks(in, stubs_out, hooks_out);
710
711 fclose(stubs_out);
712 fclose(hooks_out);
713
714 return 0;
715 }
716
717 int run_registrations(int argc,
718 char **argv)
719 {
720 char buf[MAX_PATH];
721 int i;
722
723 if (argc < 4)
724 {
725 puts(HELP);
726 return 1;
727 }
728
729 strcpy(buf, convert_path(argv[1]));
730 if (buf[strlen(buf)] != DIR_SEPARATOR_CHAR)
731 {
732 int i = strlen(buf);
733 buf[strlen(buf)] = DIR_SEPARATOR_CHAR;
734 buf[i + 1] = 0;
735 }
736 path = buf;
737 if (path[0] == 0)
738 {
739 printf("Missing path\n");
740 return 1;
741 }
742
743 file = convert_path(argv[2]);
744 if (file[0] == 0)
745 {
746 printf("Missing file\n");
747 return 1;
748 }
749
750 makefile = convert_path(argv[3]);
751 if (makefile[0] == 0)
752 {
753 printf("Missing makefile\n");
754 return 1;
755 }
756
757 exestubfile = NULL;
758 for (i = 4; i < argc; i++)
759 {
760 if (argv[i][0] == '-')
761 {
762 if (argv[i][1] == 'e')
763 {
764 exestubfile = convert_path(argv[++i]);
765 if (exestubfile[0] == 0)
766 {
767 printf("Missing exestubfile\n");
768 return 1;
769 }
770 }
771 else
772 {
773 printf("Unknown switch -%c\n", argv[i][1]);
774 return 1;
775 }
776 }
777 }
778
779
780 /* Registration file */
781 out = fopen(file, "wb");
782 if (out == NULL)
783 {
784 perror("Cannot create output file");
785 return 1;
786 }
787
788 write_line("/* This file is autogenerated. */");
789 write_line("");
790 write_line("typedef int (*TestRoutine)(int Command, char *Buffer);");
791 write_line("");
792
793 make_file_list(0);
794
795 write_line("");
796 write_line("extern void AddTest(TestRoutine Routine);");
797 write_line("");
798 write_line("void RegisterTests()");
799 write_line("{");
800
801 make_file_list(1);
802
803 write_line("}");
804
805 fclose(out);
806
807
808 /* Makefile */
809 out = fopen(makefile, "wb");
810 if (out == NULL)
811 {
812 perror("Cannot create output makefile");
813 return 1;
814 }
815
816 write_line("# This file is autogenerated.");
817 write_line("");
818 write_line("TESTS = \\");
819
820 make_file_list(2);
821
822 write_line("");
823
824 fclose(out);
825
826 /* Executable stubfile */
827 if (exestubfile != NULL)
828 {
829 if (write_file_if_changed(exestubfile, EXESTUB) != 0)
830 {
831 perror("Cannot create output executable stubfile");
832 return 1;
833 }
834 }
835
836 return 0;
837 }
838
839 int main(int argc,
840 char **argv)
841 {
842 if (argc < 2)
843 {
844 puts(HELP);
845 return 1;
846 }
847
848 if (strlen(argv[1]) > 1 && argv[1][0] == '-' && argv[1][1] == 's')
849 {
850 return run_stubs(argc, argv);
851 }
852 else
853 {
854 return run_registrations(argc, argv);
855 }
856 }