2004-10-24 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 \"regtests.h\"\n"
435 "\n"
436 "void\n"
437 "ConsoleWrite(char *Buffer)\n"
438 "{\n"
439 " printf(Buffer);\n"
440 "}\n"
441 "\n"
442 "int\n"
443 "mainCRTStartup(HANDLE hInstance,\n"
444 " HANDLE hPrevInstance,\n"
445 " LPSTR lpszCmdParam,\n"
446 " int nCmdShow)\n"
447 "{\n"
448 " InitializeTests();\n"
449 " RegisterTests();\n"
450 " SetupOnce();\n"
451 " PerformTests(ConsoleWrite, NULL);\n"
452 " _ExitProcess(0);\n"
453 " return 0;\n"
454 "}\n";
455
456 static char STUBS_HEADER[] =
457 "/* This file is autogenerated. */\n"
458 "passthrough:\n"
459 " call _FrameworkGetHook@4\n"
460 " test %eax, %eax\n"
461 " je .return\n"
462 " jmp *%eax\n"
463 ".return:\n"
464 " /* This will most likely corrupt the stack */\n"
465 " ret\n"
466 "\n";
467
468 static char HOOKS_HEADER[] =
469 "/* This file is autogenerated. */\n"
470 "#include <windows.h>\n"
471 "#include \"regtests.h\"\n"
472 "\n"
473 "API_DESCRIPTION ExternalDependencies[] =\n"
474 "{\n";
475
476 static char HOOKS_FOOTER[] =
477 "};\n"
478 "\n"
479 "#define ExternalDependencyCount %d\n"
480 "ULONG MaxExternalDependency = ExternalDependencyCount - 1;\n";
481
482 static char HELP[] =
483 "REGTESTS path file makefile [-e exestubfile]\n"
484 "REGTESTS -s stublistfile stubsfile hooksfile\n"
485 "\n"
486 " path Path to files\n"
487 " file Registration file to create\n"
488 " makefile Makefile to create\n"
489 " exestubfile Optional stub for running tests in the build environment\n"
490 " stublistfile File with descriptions of stubs\n"
491 " stubsfile File with stubs to create\n"
492 " hooksfile File with hooks to create\n";
493
494 #define INPUT_BUFFER_SIZE 255
495
496 void
497 write_stubs_header(FILE * out)
498 {
499 fputs(STUBS_HEADER, out);
500 }
501
502 void
503 write_hooks_header(FILE * out)
504 {
505 fputs(HOOKS_HEADER, out);
506 }
507
508 void
509 write_hooks_footer(FILE *hooks_out, unsigned long nr_stubs)
510 {
511 fprintf(hooks_out, HOOKS_FOOTER, nr_stubs);
512 }
513
514 char *
515 get_symbolname(char *decoratedname)
516 {
517 char buf[300];
518
519 if (decoratedname[0] == '@')
520 return strdup(decoratedname);
521 strcpy(buf, "_");
522 strcat(buf, decoratedname);
523 return strdup(buf);
524 }
525
526 char *
527 get_undecorated_name(char *buf,
528 char *decoratedname)
529 {
530 int start = 0;
531 int end = 0;
532
533 while (start < strlen(decoratedname) && decoratedname[start] == '@')
534 {
535 start++;
536 }
537 strcpy(buf, &decoratedname[start]);
538 end = strlen(buf) - 1;
539 while (end > 0 && isdigit(buf[end]))
540 {
541 end--;
542 }
543 if (buf[end] == '@')
544 {
545 buf[end] = 0;
546 }
547 return buf;
548 }
549
550 char *
551 get_forwarded_export(char *forwardedexport)
552 {
553 char buf[300];
554
555 if (forwardedexport == NULL)
556 {
557 strcpy(buf, "NULL");
558 }
559 else
560 {
561 sprintf(buf, "\"%s\"", forwardedexport);
562 }
563 return strdup(buf);
564 }
565
566 void
567 write_stub(FILE *stubs_out, FILE *hooks_out, char *dllname,
568 char *decoratedname_and_forward, unsigned int stub_index)
569 {
570 char buf[300];
571 char *p;
572 char *decoratedname = NULL;
573 char *forwardedexport = NULL;
574 char *symbolname = NULL;
575
576 p = strtok(decoratedname_and_forward, "=");
577 if (p != NULL)
578 {
579 decoratedname = p;
580
581 p = strtok(NULL, "=");
582 forwardedexport = p;
583 }
584 else
585 {
586 decoratedname = decoratedname_and_forward;
587 forwardedexport = decoratedname_and_forward;
588 }
589
590 symbolname = get_symbolname(decoratedname);
591 fprintf(stubs_out, ".globl %s\n", symbolname);
592 fprintf(stubs_out, "%s:\n", symbolname);
593 free(symbolname);
594 fprintf(stubs_out, " pushl $%d\n", stub_index);
595 fprintf(stubs_out, " jmp passthrough\n");
596 fprintf(stubs_out, "\n");
597 forwardedexport = get_forwarded_export(forwardedexport);
598 fprintf(hooks_out, " {\"%s\", \"%s\", %s, NULL, NULL},\n",
599 dllname,
600 get_undecorated_name(buf, decoratedname),
601 forwardedexport);
602 free(forwardedexport);
603 }
604
605 void
606 create_stubs_and_hooks(
607 FILE *in,
608 FILE *stubs_out,
609 FILE *hooks_out)
610 {
611 char line[INPUT_BUFFER_SIZE];
612 char *s;
613 char *dllname;
614 char *decoratedname_and_forward;
615 int stub_index;
616
617 write_stubs_header(stubs_out);
618
619 write_hooks_header(hooks_out);
620
621 /*
622 * Scan the database. The database is a text file; each
623 * line is a record, which contains data for one stub.
624 * Each record has two columns:
625 *
626 * DLLNAME (e.g. ntdll.dll)
627 * DECORATED NAME (e.g. NtCreateProcess@32, @InterlockedIncrement@4 or printf)
628 */
629 stub_index = 0; /* First stub has index zero */
630
631 for (
632 ;
633 /* Go on until EOF or read zero bytes */
634 ((!feof(in)) && (fgets(line, sizeof line, in) != NULL));
635 /* Next stub index */
636 )
637 {
638 /*
639 * Remove, if present, the trailing LF.
640 */
641 if ((s = (char *) strchr(line,'\n')) != NULL)
642 {
643 *s = '\0';
644 }
645
646 /*
647 * Remove, if present, the trailing CR.
648 */
649 if ((s = (char *) strchr(line,'\r')) != NULL)
650 {
651 *s = '\0';
652 }
653
654 /*
655 * Skip comments (#) and empty lines.
656 */
657 s = & line[0];
658 if ((*s) != '#' && (*s) != '\0')
659 {
660 /* Extract the DLL name */
661 dllname = (char *) strtok(s, " \t");
662 if (dllname != NULL && strlen(dllname) > 0)
663 {
664 /*
665 * Extract the decorated function name and possibly forwarded export.
666 * Format:
667 * decoratedname=forwardedexport (no DLL name)
668 */
669 decoratedname_and_forward = (char *) strtok(NULL, " \t");
670 /* Extract the argument count */
671 write_stub(stubs_out, hooks_out, dllname, decoratedname_and_forward, stub_index);
672 stub_index++;
673 }
674 }
675 }
676
677 write_hooks_footer(hooks_out, stub_index);
678 }
679
680 int run_stubs(int argc,
681 char **argv)
682 {
683 FILE *in;
684 FILE *stubs_out;
685 FILE *hooks_out;
686
687 in = fopen(argv[2], "rb");
688 if (in == NULL)
689 {
690 perror("Failed to open stub description input file");
691 return 1;
692 }
693
694 stubs_out = fopen(argv[3], "wb");
695 if (stubs_out == NULL)
696 {
697 perror("Failed to open stubs output file");
698 return 1;
699 }
700
701 hooks_out = fopen(argv[4], "wb");
702 if (hooks_out == NULL)
703 {
704 perror("Failed to open hooks output file");
705 return 1;
706 }
707
708 create_stubs_and_hooks(in, stubs_out, hooks_out);
709
710 fclose(stubs_out);
711 fclose(hooks_out);
712
713 return 0;
714 }
715
716 int run_registrations(int argc,
717 char **argv)
718 {
719 char buf[MAX_PATH];
720 int i;
721
722 if (argc < 4)
723 {
724 puts(HELP);
725 return 1;
726 }
727
728 strcpy(buf, convert_path(argv[1]));
729 if (buf[strlen(buf)] != DIR_SEPARATOR_CHAR)
730 {
731 int i = strlen(buf);
732 buf[strlen(buf)] = DIR_SEPARATOR_CHAR;
733 buf[i + 1] = 0;
734 }
735 path = buf;
736 if (path[0] == 0)
737 {
738 printf("Missing path\n");
739 return 1;
740 }
741
742 file = convert_path(argv[2]);
743 if (file[0] == 0)
744 {
745 printf("Missing file\n");
746 return 1;
747 }
748
749 makefile = convert_path(argv[3]);
750 if (makefile[0] == 0)
751 {
752 printf("Missing makefile\n");
753 return 1;
754 }
755
756 exestubfile = NULL;
757 for (i = 4; i < argc; i++)
758 {
759 if (argv[i][0] == '-')
760 {
761 if (argv[i][1] == 'e')
762 {
763 exestubfile = convert_path(argv[++i]);
764 if (exestubfile[0] == 0)
765 {
766 printf("Missing exestubfile\n");
767 return 1;
768 }
769 }
770 else
771 {
772 printf("Unknown switch -%c\n", argv[i][1]);
773 return 1;
774 }
775 }
776 }
777
778
779 /* Registration file */
780 out = fopen(file, "wb");
781 if (out == NULL)
782 {
783 perror("Cannot create output file");
784 return 1;
785 }
786
787 write_line("/* This file is autogenerated. */");
788 write_line("");
789 write_line("typedef int (*TestRoutine)(int Command, char *Buffer);");
790 write_line("");
791
792 make_file_list(0);
793
794 write_line("");
795 write_line("extern void AddTest(TestRoutine Routine);");
796 write_line("");
797 write_line("void RegisterTests()");
798 write_line("{");
799
800 make_file_list(1);
801
802 write_line("}");
803
804 fclose(out);
805
806
807 /* Makefile */
808 out = fopen(makefile, "wb");
809 if (out == NULL)
810 {
811 perror("Cannot create output makefile");
812 return 1;
813 }
814
815 write_line("# This file is autogenerated.");
816 write_line("");
817 write_line("TESTS = \\");
818
819 make_file_list(2);
820
821 write_line("");
822
823 fclose(out);
824
825 /* Executable stubfile */
826 if (exestubfile != NULL)
827 {
828 if (write_file_if_changed(exestubfile, EXESTUB) != 0)
829 {
830 perror("Cannot create output executable stubfile");
831 return 1;
832 }
833 }
834
835 return 0;
836 }
837
838 int main(int argc,
839 char **argv)
840 {
841 if (argc < 2)
842 {
843 puts(HELP);
844 return 1;
845 }
846
847 if (strlen(argv[1]) > 1 && argv[1][0] == '-' && argv[1][1] == 's')
848 {
849 return run_stubs(argc, argv);
850 }
851 else
852 {
853 return run_registrations(argc, argv);
854 }
855 }