[AMD64]
[reactos.git] / reactos / tools / nci / ncitool.c
1 /*
2 * FILE: tools/nci/ncitool.c
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: Native Call Interface Support Tool
5 * PURPOSE: Generates NCI Tables and Stubs.
6 * PROGRAMMER; Alex Ionescu (alex@relsoft.net)
7 * CHANGE HISTORY: 14/01/05 - Created. Based on original code by
8 * KJK::Hyperion and Emanuelle Aliberti.
9 *
10 */
11
12 /* INCLUDE ******************************************************************/
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #if !defined(__FreeBSD__) && !defined(__APPLE__)
18 # include <malloc.h>
19 #endif // __FreeBSD__
20
21 /* DEFINES ****************************************************************/
22
23 #define INPUT_BUFFER_SIZE 255
24 #define Arguments 8
25
26 /******* Table Indexes ************/
27 #define MAIN_INDEX 0x0
28 #define WIN32K_INDEX 0x1000
29
30 /******* Argument List ************/
31 /* Databases */
32 #define NativeSystemDb 0
33 #define NativeGuiDb 1
34
35 /* Service Tables */
36 #define NtosServiceTable 2
37 #define Win32kServiceTable 3
38
39 /* Stub Files */
40 #define NtosUserStubs 4
41 #define NtosKernelStubs 5
42 #define Win32kStubs 6
43
44 /* Spec Files */
45 #define NtSpec 7
46
47 /********** Stub Code ************/
48
49 /*
50 * This stubs calls into KUSER_SHARED_DATA where either a
51 * sysenter or interrupt is performed, depending on CPU support.
52 */
53 #if defined(__GNUC__)
54 #define UserModeStub_x86 " movl $0x%x, %%eax\n" \
55 " movl $KUSER_SHARED_SYSCALL, %%ecx\n" \
56 " call *(%%ecx)\n" \
57 " ret $0x%x\n\n"
58
59 #define UserModeStub_amd64 " movl $0x%x, %%eax\n" \
60 " movq %%rcx, %%r10\n" \
61 " syscall\n" \
62 " ret $0x%x\n\n"
63
64 #define UserModeStub_ppc " stwu 1,-16(1)\n" \
65 " mflr 0\n\t" \
66 " stw 0,0(1)\n" \
67 " li 0,0x%x\n" \
68 " sc\n" \
69 " lwz 0,0(1)\n" \
70 " mtlr 0\n" \
71 " addi 1,1,16\n" \
72 " blr\n"
73
74 #define UserModeStub_mips " li $8, KUSER_SHARED_SYSCALL\n" \
75 " lw $8,0($8)\n" \
76 " j $8\n" \
77 " nop\n"
78
79 #define UserModeStub_arm " swi #0x%x\n" \
80 " bx lr\n\n"
81
82 #elif defined(_MSC_VER)
83 #define UserModeStub_x86 " asm { \n" \
84 " mov eax, %xh\n" \
85 " mov ecx, KUSER_SHARED_SYSCALL\n" \
86 " call [ecx]\n" \
87 " ret %xh\n" \
88 " }\n"
89 #else
90 #error Unknown compiler for inline assembler
91 #endif
92
93 /*
94 * This stub calls KiSystemService directly with a fake INT2E stack.
95 * Because EIP is pushed during the call, the handler will return here.
96 */
97 #if defined(__GNUC__)
98 #define KernelModeStub_x86 " movl $0x%x, %%eax\n" \
99 " leal 4(%%esp), %%edx\n" \
100 " pushfl\n" \
101 " pushl $KGDT_R0_CODE\n" \
102 " call _KiSystemService\n" \
103 " ret $0x%x\n\n"
104
105 #define KernelModeStub_amd64 " movl $0x%x, %%eax\n" \
106 " call KiSystemService\n" \
107 " ret $0x%x\n\n"
108
109 /* For now, use the usermode stub. We'll optimize later */
110 #define KernelModeStub_ppc UserModeStub_ppc
111
112 #define KernelModeStub_mips " j KiSystemService\n" \
113 " nop\n"
114
115 #define KernelModeStub_arm " mov ip, lr\n" \
116 " swi #0x%x\n" \
117 " bx ip\n\n"
118
119 #elif defined(_MSC_VER)
120 #define KernelModeStub_x86 " asm { \n" \
121 " mov eax, %xh\n" \
122 " lea edx, [esp+4]\n" \
123 " pushf\n" \
124 " push KGDT_R0_CODE\n" \
125 " call _KiSystemService\n" \
126 " ret %xh\n" \
127 " }\n"
128 #else
129 #error Unknown compiler for inline assembler
130 #endif
131
132 /***** Arch Dependent Stuff ******/
133 struct ncitool_data_t {
134 const char *arch;
135 int args_to_bytes;
136 const char *km_stub;
137 const char *um_stub;
138 const char *global_header;
139 const char *declaration;
140 };
141
142 struct ncitool_data_t ncitool_data[] = {
143 { "i386", 4, KernelModeStub_x86, UserModeStub_x86,
144 ".global _%s@%d\n", "_%s@%d:\n" },
145 { "amd64", 4, KernelModeStub_amd64, UserModeStub_amd64,
146 ".global %s\n", "%s:\n" },
147 { "powerpc", 4, KernelModeStub_ppc, UserModeStub_ppc,
148 "\t.globl %s\n", "%s:\n" },
149 { "mips", 4, KernelModeStub_mips, UserModeStub_mips,
150 "\t.globl %s\n", "%s:\n" },
151 { "arm", 4, KernelModeStub_arm, UserModeStub_arm,
152 "\t.globl %s\n", "%s:\n" },
153 { 0, }
154 };
155 int arch_sel = 0;
156 #define ARGS_TO_BYTES(x) (x)*(ncitool_data[arch_sel].args_to_bytes)
157 #define UserModeStub ncitool_data[arch_sel].um_stub
158 #define KernelModeStub ncitool_data[arch_sel].km_stub
159 #define GlobalHeader ncitool_data[arch_sel].global_header
160 #define Declaration ncitool_data[arch_sel].declaration
161
162 /* FUNCTIONS ****************************************************************/
163
164 /*++
165 * WriteFileHeader
166 *
167 * Prints out the File Header for a Stub File.
168 *
169 * Params:
170 * StubFile - Stub File to which to write the header.
171 *
172 * FileDescription - Description of the Stub file to which to write the header.
173 *
174 * FileLocation - Name of the Stub file to which to write the header.
175 *
176 * Returns:
177 * None.
178 *
179 * Remarks:
180 * FileLocation is only used for printing the header.
181 *
182 *--*/
183 void
184 WriteFileHeader(FILE * StubFile,
185 char* FileDescription,
186 char* FileLocation)
187 {
188 /* This prints out the file header */
189 fprintf(StubFile,
190 "/* FILE: %s\n"
191 " * COPYRIGHT: See COPYING in the top level directory\n"
192 " * PURPOSE: %s\n"
193 " * PROGRAMMER: Computer Generated File. See tools/nci/ncitool.c\n"
194 " * REMARK: DO NOT EDIT OR COMMIT MODIFICATIONS TO THIS FILE\n"
195 " */\n\n\n"
196 "#include <ndk/asm.h>\n\n",
197 FileDescription,
198 FileLocation);
199 }
200
201 /*++
202 * WriteFileHeader
203 *
204 * Prints out the File Header for a Stub File.
205 *
206 * Params:
207 * StubFile - Stub File to which to write the header.
208 *
209 * FileDescription - Description of the Stub file to which to write the header.
210 *
211 * FileLocation - Name of the Stub file to which to write the header.
212 *
213 * Returns:
214 * None.
215 *
216 * Remarks:
217 * FileLocation is only used for printing the header.
218 *
219 *--*/
220 void
221 WriteStubHeader(FILE* StubFile,
222 char* SyscallName,
223 unsigned StackBytes)
224 {
225 /* Export the function */
226 fprintf(StubFile, GlobalHeader, SyscallName, StackBytes);
227
228 /* Define it */
229 fprintf(StubFile, Declaration, SyscallName, StackBytes);
230 }
231
232
233 /*++
234 * WriteKernelModeStub
235 *
236 * Prints out the Kernel Mode Stub for a System Call.
237 *
238 * Params:
239 * StubFile - Stub File to which to write the header.
240 *
241 * SyscallName - Name of System Call for which to add the stub.
242 *
243 * StackBytes - Number of bytes on the stack to return after doing the system call.
244 *
245 * SyscallId - Service Descriptor Table ID for this System Call.
246 *
247 * Returns:
248 * None.
249 *
250 * Remarks:
251 * On i386, StackBytes is the number of arguments x 4.
252 *
253 *--*/
254 void
255 WriteKernelModeStub(FILE* StubFile,
256 char* SyscallName,
257 unsigned StackBytes,
258 unsigned int SyscallId)
259 {
260 /* Write the Stub Header and export the Function */
261 WriteStubHeader(StubFile, SyscallName, StackBytes);
262
263 /* Write the Stub Code */
264 fprintf(StubFile, KernelModeStub, SyscallId, StackBytes);
265 }
266
267 /*++
268 * WriteUserModeStub
269 *
270 * Prints out the User Mode Stub for a System Call.
271 *
272 * Params:
273 * StubFile - Stub File to which to write the header.
274 *
275 * SyscallName - Name of System Call for which to add the stub.
276 *
277 * StackBytes - Number of bytes on the stack to return after doing the system call.
278 *
279 * SyscallId - Service Descriptor Table ID for this System Call.
280 *
281 * Returns:
282 * None.
283 *
284 * Remarks:
285 * On i386, StackBytes is the number of arguments x 4.
286 *
287 *--*/
288 void
289 WriteUserModeStub(FILE* StubFile,
290 char* SyscallName,
291 unsigned StackBytes,
292 unsigned int SyscallId)
293 {
294 /* Write the Stub Header and export the Function */
295 WriteStubHeader(StubFile, SyscallName, StackBytes);
296
297 /* Write the Stub Code */
298 fprintf(StubFile, UserModeStub, SyscallId, StackBytes);
299 }
300
301 /*++
302 * GetNameAndArgumentsFromDb
303 *
304 * Parses an entry from a System Call Database, extracting
305 * the function's name and arguments that it takes.
306 *
307 * Params:
308 * Line - Entry from the Database to parse.
309 *
310 * NtSyscallName - Output string to which to save the Function Name
311 *
312 * SyscallArguments - Output string to which to save the number of
313 * arguments that the function takes.
314 *
315 * Returns:
316 * None.
317 *
318 * Remarks:
319 * On i386, StackBytes is the number of arguments x 4.
320 *
321 *--*/
322 void
323 GetNameAndArgumentsFromDb(char Line[],
324 char ** NtSyscallName,
325 char ** SyscallArguments)
326 {
327 char *s;
328 char *stmp;
329
330 /* Remove new line */
331 if ((s = (char *) strchr(Line,'\r')) != NULL) {
332 *s = '\0';
333 }
334
335 /* Skip comments (#) and empty lines */
336 s = &Line[0];
337 if ((*s) != '#' && (*s) != '\0') {
338
339 /* Extract the NtXXX name */
340 *NtSyscallName = (char *)strtok(s," \t");
341
342 /* Extract the argument count */
343 *SyscallArguments = (char *)strtok(NULL," \t");
344
345 /* Remove, if present, the trailing LF */
346 if ((stmp = strchr(*SyscallArguments, '\n')) != NULL) {
347 *stmp = '\0';
348 }
349
350 } else {
351
352 /* Skip this entry */
353 *NtSyscallName = NULL;
354 *SyscallArguments = NULL;
355 }
356 }
357
358 /*++
359 * CreateStubs
360 *
361 * Parses a System Call Database and creates stubs for all the entries.
362 *
363 * Params:
364 * SyscallDb - System Call Database to parse.
365 *
366 * UserModeFiles - Array of Usermode Stub Files to which to write the stubs.
367 *
368 * KernelModeFile - Kernelmode Stub Files to which to write the stubs.
369 *
370 * Index - Number of first syscall
371 *
372 * UserFiles - Number of Usermode Stub Files to create
373 *
374 * NeedsZw - Write Zw prefix?
375 *
376 * Returns:
377 * None.
378 *
379 * Remarks:
380 * None.
381 *
382 *--*/
383 void
384 CreateStubs(FILE * SyscallDb,
385 FILE * UserModeFiles[],
386 FILE * KernelModeFile,
387 unsigned Index,
388 unsigned UserFiles,
389 unsigned NeedsZw)
390 {
391 char Line[INPUT_BUFFER_SIZE];
392 char *NtSyscallName;
393 char *SyscallArguments;
394 int SyscallId;
395 unsigned StackBytes;
396
397 /* We loop, incrementing the System Call Index, until the end of the file */
398 for (SyscallId = 0; ((!feof(SyscallDb)) && (fgets(Line, sizeof(Line), SyscallDb) != NULL));) {
399
400 /* Extract the Name and Arguments */
401 GetNameAndArgumentsFromDb(Line, &NtSyscallName, &SyscallArguments);
402 if (SyscallArguments != NULL)
403 StackBytes = ARGS_TO_BYTES(strtoul(SyscallArguments, NULL, 0));
404 else
405 StackBytes = 0;
406
407 /* Make sure we really extracted something */
408 if (NtSyscallName) {
409
410 /* Create Usermode Stubs for Nt/Zw syscalls in each Usermode file */
411 int i;
412 for (i= 0; i < UserFiles; i++) {
413
414 /* Write the Nt Version */
415 WriteUserModeStub(UserModeFiles[i],
416 NtSyscallName,
417 StackBytes,
418 SyscallId | Index);
419
420 /* If a Zw Version is needed (was specified), write it too */
421 if (NeedsZw) {
422
423 NtSyscallName[0] = 'Z';
424 NtSyscallName[1] = 'w';
425 WriteUserModeStub(UserModeFiles[i],
426 NtSyscallName,
427 StackBytes,
428 SyscallId | Index);
429 }
430
431 }
432
433 /* Create the Kernel coutnerparts (only Zw*, Nt* are the real functions!) */
434 if (KernelModeFile) {
435
436 NtSyscallName[0] = 'Z';
437 NtSyscallName[1] = 'w';
438 WriteKernelModeStub(KernelModeFile,
439 NtSyscallName,
440 StackBytes,
441 SyscallId | Index);
442 }
443
444 /* Only increase if we actually added something */
445 SyscallId++;
446 }
447 }
448 }
449
450 /*++
451 * CreateSystemServiceTable
452 *
453 * Parses a System Call Database and creates a System Call Service Table for it.
454 *
455 * Params:
456 * SyscallDb - System Call Database to parse.
457 *
458 * SyscallTable - File in where to create System Call Service Table.
459 *
460 * Name - Name of the Service Table.
461 *
462 * FileLocation - Filename containing the Table.
463 *
464 * Returns:
465 * None.
466 *
467 * Remarks:
468 * FileLocation is only used for the header generation.
469 *
470 *--*/
471 void
472 CreateSystemServiceTable(FILE *SyscallDb,
473 FILE *SyscallTable,
474 char * Name,
475 char * FileLocation)
476 {
477 char Line[INPUT_BUFFER_SIZE];
478 char *NtSyscallName;
479 char *SyscallArguments;
480 int SyscallId;
481
482 /* Print the Header */
483 WriteFileHeader(SyscallTable, "System Call Table for Native API", FileLocation);
484
485 /* First we build the SSDT */
486 fprintf(SyscallTable,"\n\n\n");
487 fprintf(SyscallTable,"ULONG_PTR %sSSDT[] = {\n", Name);
488
489 /* We loop, incrementing the System Call Index, until the end of the file */
490 for (SyscallId = 0; ((!feof(SyscallDb)) && (fgets(Line, sizeof(Line), SyscallDb) != NULL));) {
491
492 /* Extract the Name and Arguments */
493 GetNameAndArgumentsFromDb(Line, &NtSyscallName, &SyscallArguments);
494
495 /* Make sure we really extracted something */
496 if (NtSyscallName) {
497
498 /* Add a new line */
499 if (SyscallId > 0) fprintf(SyscallTable,",\n");
500
501 /* Write the syscall name in the service table. */
502 fprintf(SyscallTable,"\t\t(ULONG_PTR)%s", NtSyscallName);
503
504 /* Only increase if we actually added something */
505 SyscallId++;
506 }
507 }
508
509 /* Close the service table (C syntax) */
510 fprintf(SyscallTable,"\n};\n");
511
512 /* Now we build the SSPT */
513 rewind(SyscallDb);
514 fprintf(SyscallTable,"\n\n\n");
515 fprintf(SyscallTable,"UCHAR %sSSPT[] = {\n", Name);
516
517 for (SyscallId = 0; ((!feof(SyscallDb)) && (fgets(Line, sizeof(Line), SyscallDb) != NULL));) {
518
519 /* Extract the Name and Arguments */
520 GetNameAndArgumentsFromDb(Line, &NtSyscallName, &SyscallArguments);
521
522 /* Make sure we really extracted something */
523 if (NtSyscallName) {
524
525 /* Add a new line */
526 if (SyscallId > 0) fprintf(SyscallTable,",\n");
527
528 /* Write the syscall arguments in the argument table. */
529 if (SyscallArguments != NULL)
530 fprintf(SyscallTable,"\t\t%lu * sizeof(void *)",strtoul(SyscallArguments, NULL, 0));
531 else
532 fprintf(SyscallTable,"\t\t0");
533
534 /* Only increase if we actually added something */
535 SyscallId++;
536 }
537 }
538
539 /* Close the service table (C syntax) */
540 fprintf(SyscallTable,"\n};\n");
541
542 /*
543 * We write some useful defines
544 */
545 fprintf(SyscallTable, "\n\n#define MIN_SYSCALL_NUMBER 0\n");
546 fprintf(SyscallTable, "#define MAX_SYSCALL_NUMBER %d\n", SyscallId - 1);
547 fprintf(SyscallTable, "#define NUMBER_OF_SYSCALLS %d\n", SyscallId);
548 fprintf(SyscallTable, "ULONG %sNumberOfSysCalls = %d;\n", Name, SyscallId);
549 }
550
551 /*++
552 * WriteSpec
553 *
554 * Prints out the Spec Entry for a System Call.
555 *
556 * Params:
557 * SpecFile - Spec File to which to write the header.
558 *
559 * SyscallName - Name of System Call for which to add the stub.
560 *
561 * CountArguments - Number of arguments to the System Call.
562 *
563 * Returns:
564 * None.
565 *
566 * Remarks:
567 * None.
568 *
569 *--*/
570 void
571 WriteSpec(FILE* StubFile,
572 char* SyscallName,
573 unsigned CountArguments)
574 {
575 unsigned i;
576
577 fprintf(StubFile, "@ stdcall %s", SyscallName);
578
579 fputc ('(', StubFile);
580
581 for (i = 0; i < CountArguments; ++ i)
582 fputs ("ptr ", StubFile);
583
584 fputc (')', StubFile);
585 fputc ('\n', StubFile);
586 }
587
588 /*++
589 * CreateSpec
590 *
591 * Parses a System Call Database and creates a spec file for all the entries.
592 *
593 * Params:
594 * SyscallDb - System Call Database to parse.
595 *
596 * Files - Array of Spec Files to which to write.
597 *
598 * CountFiles - Number of Spec Files to create
599 *
600 * UseZw - Use Zw prefix?
601 *
602 * Returns:
603 * None.
604 *
605 * Remarks:
606 * None.
607 *
608 *--*/
609 void
610 CreateSpec(FILE * SyscallDb,
611 FILE * Files[],
612 unsigned CountFiles,
613 unsigned UseZw)
614 {
615 char Line[INPUT_BUFFER_SIZE];
616 char *NtSyscallName;
617 char *SyscallArguments;
618 unsigned CountArguments;
619
620 /* We loop until the end of the file */
621 while ((!feof(SyscallDb)) && (fgets(Line, sizeof(Line), SyscallDb) != NULL)) {
622
623 /* Extract the Name and Arguments */
624 GetNameAndArgumentsFromDb(Line, &NtSyscallName, &SyscallArguments);
625 CountArguments = strtoul(SyscallArguments, NULL, 0);
626
627 /* Make sure we really extracted something */
628 if (NtSyscallName) {
629
630 int i;
631 for (i= 0; i < CountFiles; i++) {
632
633 if (!UseZw) {
634 WriteSpec(Files[i],
635 NtSyscallName,
636 CountArguments);
637 }
638
639 if (UseZw && NtSyscallName[0] == 'N' && NtSyscallName[1] == 't') {
640
641 NtSyscallName[0] = 'Z';
642 NtSyscallName[1] = 'w';
643 WriteSpec(Files[i],
644 NtSyscallName,
645 CountArguments);
646 }
647
648 }
649 }
650 }
651 }
652
653 void usage(char * argv0)
654 {
655 printf("Usage: %s [-arch <arch>] sysfuncs.lst w32ksvc.db napi.h ssdt.h napi.S zw.S win32k.S win32k.S\n"
656 " sysfuncs.lst native system functions database\n"
657 " w32ksvc.db native graphic functions database\n"
658 " napi.h NTOSKRNL service table\n"
659 " ssdt.h WIN32K service table\n"
660 " napi.S NTDLL stubs\n"
661 " zw.S NTOSKRNL Zw stubs\n"
662 " win32k.S GDI32 stubs\n"
663 " win32k.S USER32 stubs\n"
664 " nt.pspec NTDLL exports\n"
665 " -arch is optional, default is %s\n",
666 argv0,
667 ncitool_data[0].arch
668 );
669 }
670
671 int main(int argc, char* argv[])
672 {
673 FILE * Files[Arguments] = { };
674 int FileNumber, ArgOffset = 1;
675 char * OpenType = "r";
676
677 /* Catch architecture argument */
678 if (argc > 3 && !strcmp(argv[1],"-arch")) {
679 for( arch_sel = 0; ncitool_data[arch_sel].arch; arch_sel++ )
680 if (strcmp(argv[2],ncitool_data[arch_sel].arch) == 0)
681 break;
682 if (!ncitool_data[arch_sel].arch) {
683 printf("Invalid arch '%s'\n", argv[2]);
684 usage(argv[0]);
685 return 1;
686 }
687 ArgOffset = 3;
688 }
689 /* Make sure all arguments all there */
690 if (argc != Arguments + ArgOffset) {
691 usage(argv[0]);
692 return(1);
693 }
694
695 /* Open all Output and bail out if any fail */
696 for (FileNumber = 0; FileNumber < Arguments; FileNumber++) {
697
698 /* Open the File */
699 if (FileNumber == 2) OpenType = "wb";
700 Files[FileNumber] = fopen(argv[FileNumber + ArgOffset], OpenType);
701
702 /* Check for failure and error out if so */
703 if (!Files[FileNumber]) {
704 perror(argv[FileNumber + ArgOffset]);
705 return (1);
706 }
707 }
708
709 /* Write the File Headers */
710 WriteFileHeader(Files[NtosUserStubs],
711 "System Call Stubs for Native API",
712 argv[NtosUserStubs + ArgOffset]);
713
714 WriteFileHeader(Files[NtosKernelStubs],
715 "System Call Stubs for Native API",
716 argv[NtosKernelStubs + ArgOffset]);
717 fputs("#include <ndk/asm.h>\n\n", Files[NtosKernelStubs]);
718
719 WriteFileHeader(Files[Win32kStubs],
720 "System Call Stubs for Native API",
721 argv[Win32kStubs + ArgOffset]);
722
723 /* Create the System Stubs */
724 CreateStubs(Files[NativeSystemDb],
725 &Files[NtosUserStubs],
726 Files[NtosKernelStubs],
727 MAIN_INDEX,
728 1,
729 1);
730
731 /* Create the Graphics Stubs */
732 CreateStubs(Files[NativeGuiDb],
733 &Files[Win32kStubs],
734 NULL,
735 WIN32K_INDEX,
736 1,
737 0);
738
739 /* Create the Service Tables */
740 rewind(Files[NativeSystemDb]);
741 CreateSystemServiceTable(Files[NativeSystemDb],
742 Files[NtosServiceTable],
743 "Main",
744 argv[NtosServiceTable + ArgOffset]);
745
746 rewind(Files[NativeGuiDb]);
747 CreateSystemServiceTable(Files[NativeGuiDb],
748 Files[Win32kServiceTable],
749 "Win32k",
750 argv[Win32kServiceTable + ArgOffset]);
751
752 /* Create the Spec Files */
753 rewind(Files[NativeSystemDb]);
754 CreateSpec(Files[NativeSystemDb],
755 &Files[NtSpec],
756 1,
757 0);
758
759 rewind(Files[NativeSystemDb]);
760 CreateSpec(Files[NativeSystemDb],
761 &Files[NtSpec],
762 1,
763 1);
764
765 /* Close all files */
766 for (FileNumber = 0; FileNumber < Arguments-ArgOffset; FileNumber++) {
767
768 /* Close the File */
769 fclose(Files[FileNumber]);
770
771 }
772
773 return(0);
774 }