Create a branch for header work.
[reactos.git] / dll / win32 / kernel32 / misc / ldr.c
1 /* $Id$
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT : ReactOS user mode libraries
5 * MODULE : kernel32.dll
6 * FILE : reactos/lib/kernel32/misc/ldr.c
7 * AUTHOR : Ariadne
8 */
9
10 #include <k32.h>
11
12 #define NDEBUG
13 #include <debug.h>
14
15 typedef struct tagLOADPARMS32 {
16 LPSTR lpEnvAddress;
17 LPSTR lpCmdLine;
18 LPSTR lpCmdShow;
19 DWORD dwReserved;
20 } LOADPARMS32;
21
22 extern BOOLEAN InWindows;
23 extern WaitForInputIdleType lpfnGlobalRegisterWaitForInputIdle;
24
25 /* FUNCTIONS ****************************************************************/
26
27 /**
28 * @name GetDllLoadPath
29 *
30 * Internal function to compute the load path to use for a given dll.
31 *
32 * @remarks Returned pointer must be freed by caller.
33 */
34
35 LPWSTR
36 GetDllLoadPath(LPCWSTR lpModule)
37 {
38 ULONG Pos = 0, Length = 0;
39 PWCHAR EnvironmentBufferW = NULL;
40 LPCWSTR lpModuleEnd = NULL;
41 UNICODE_STRING ModuleName;
42 DWORD LastError = GetLastError(); /* GetEnvironmentVariable changes LastError */
43
44 if ((lpModule != NULL) && (wcslen(lpModule) > 2) && (lpModule[1] == ':'))
45 {
46 lpModuleEnd = lpModule + wcslen(lpModule);
47 }
48 else
49 {
50 ModuleName = NtCurrentPeb()->ProcessParameters->ImagePathName;
51 lpModule = ModuleName.Buffer;
52 lpModuleEnd = lpModule + (ModuleName.Length / sizeof(WCHAR));
53 }
54
55 if (lpModule != NULL)
56 {
57 while (lpModuleEnd > lpModule && *lpModuleEnd != L'/' &&
58 *lpModuleEnd != L'\\' && *lpModuleEnd != L':')
59 {
60 --lpModuleEnd;
61 }
62 Length = (lpModuleEnd - lpModule) + 1;
63 }
64
65 Length += GetCurrentDirectoryW(0, NULL);
66 Length += GetDllDirectoryW(0, NULL);
67 Length += GetSystemDirectoryW(NULL, 0);
68 Length += GetWindowsDirectoryW(NULL, 0);
69 Length += GetEnvironmentVariableW(L"PATH", NULL, 0);
70
71 EnvironmentBufferW = RtlAllocateHeap(RtlGetProcessHeap(), 0,
72 Length * sizeof(WCHAR));
73 if (EnvironmentBufferW == NULL)
74 {
75 return NULL;
76 }
77
78 if (lpModule)
79 {
80 RtlCopyMemory(EnvironmentBufferW, lpModule,
81 (lpModuleEnd - lpModule) * sizeof(WCHAR));
82 Pos += lpModuleEnd - lpModule;
83 EnvironmentBufferW[Pos++] = L';';
84 }
85
86 Pos += GetCurrentDirectoryW(Length, EnvironmentBufferW + Pos);
87 EnvironmentBufferW[Pos++] = L';';
88 Pos += GetDllDirectoryW(Length - Pos, EnvironmentBufferW + Pos);
89 EnvironmentBufferW[Pos++] = L';';
90 Pos += GetSystemDirectoryW(EnvironmentBufferW + Pos, Length - Pos);
91 EnvironmentBufferW[Pos++] = L';';
92 Pos += GetWindowsDirectoryW(EnvironmentBufferW + Pos, Length - Pos);
93 EnvironmentBufferW[Pos++] = L';';
94 Pos += GetEnvironmentVariableW(L"PATH", EnvironmentBufferW + Pos, Length - Pos);
95
96 SetLastError(LastError);
97 return EnvironmentBufferW;
98 }
99
100 /*
101 * @implemented
102 */
103 BOOL
104 WINAPI
105 DisableThreadLibraryCalls (
106 HMODULE hLibModule
107 )
108 {
109 NTSTATUS Status;
110
111 Status = LdrDisableThreadCalloutsForDll ((PVOID)hLibModule);
112 if (!NT_SUCCESS (Status))
113 {
114 SetLastErrorByStatus (Status);
115 return FALSE;
116 }
117 return TRUE;
118 }
119
120
121 /*
122 * @implemented
123 */
124 HINSTANCE
125 WINAPI
126 LoadLibraryA (
127 LPCSTR lpLibFileName
128 )
129 {
130 return LoadLibraryExA (lpLibFileName, 0, 0);
131 }
132
133
134 /*
135 * @implemented
136 */
137 HINSTANCE
138 WINAPI
139 LoadLibraryExA (
140 LPCSTR lpLibFileName,
141 HANDLE hFile,
142 DWORD dwFlags
143 )
144 {
145 PWCHAR FileNameW;
146
147 if (!(FileNameW = FilenameA2W(lpLibFileName, FALSE)))
148 return FALSE;
149
150 return LoadLibraryExW(FileNameW, hFile, dwFlags);
151 }
152
153
154 /*
155 * @implemented
156 */
157 HINSTANCE
158 WINAPI
159 LoadLibraryW (
160 LPCWSTR lpLibFileName
161 )
162 {
163 return LoadLibraryExW (lpLibFileName, 0, 0);
164 }
165
166
167 static
168 NTSTATUS
169 LoadLibraryAsDatafile(PWSTR path, LPCWSTR name, HMODULE* hmod)
170 {
171 static const WCHAR dotDLL[] = {'.','d','l','l',0};
172
173 WCHAR filenameW[MAX_PATH];
174 HANDLE hFile = INVALID_HANDLE_VALUE;
175 HANDLE mapping;
176 HMODULE module;
177
178 *hmod = 0;
179
180 if (!SearchPathW( path, name, dotDLL, sizeof(filenameW) / sizeof(filenameW[0]),
181 filenameW, NULL ))
182 {
183 return NtCurrentTeb()->LastStatusValue;
184 }
185
186 hFile = CreateFileW( filenameW, GENERIC_READ, FILE_SHARE_READ,
187 NULL, OPEN_EXISTING, 0, 0 );
188
189 if (hFile == INVALID_HANDLE_VALUE) return NtCurrentTeb()->LastStatusValue;
190
191 mapping = CreateFileMappingW( hFile, NULL, PAGE_READONLY, 0, 0, NULL );
192 CloseHandle( hFile );
193 if (!mapping) return NtCurrentTeb()->LastStatusValue;
194
195 module = MapViewOfFile( mapping, FILE_MAP_READ, 0, 0, 0 );
196 CloseHandle( mapping );
197 if (!module) return NtCurrentTeb()->LastStatusValue;
198
199 /* make sure it's a valid PE file */
200 if (!RtlImageNtHeader(module))
201 {
202 UnmapViewOfFile( module );
203 return STATUS_INVALID_IMAGE_FORMAT;
204 }
205 *hmod = (HMODULE)((char *)module + 1); /* set low bit of handle to indicate datafile module */
206 return STATUS_SUCCESS;
207 }
208
209
210 /*
211 * @implemented
212 */
213 HINSTANCE
214 WINAPI
215 LoadLibraryExW (
216 LPCWSTR lpLibFileName,
217 HANDLE hFile,
218 DWORD dwFlags
219 )
220 {
221 UNICODE_STRING DllName;
222 HINSTANCE hInst;
223 NTSTATUS Status;
224 PWSTR SearchPath;
225 ULONG DllCharacteristics;
226 BOOL FreeString = FALSE;
227
228 (void)hFile;
229
230 if ( lpLibFileName == NULL )
231 return NULL;
232
233 /* Check for any flags LdrLoadDll might be interested in */
234 if (dwFlags & DONT_RESOLVE_DLL_REFERENCES)
235 {
236 /* Tell LDR to treat it as an EXE */
237 DllCharacteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
238 }
239
240 dwFlags &=
241 DONT_RESOLVE_DLL_REFERENCES |
242 LOAD_LIBRARY_AS_DATAFILE |
243 LOAD_WITH_ALTERED_SEARCH_PATH;
244
245 SearchPath = GetDllLoadPath(
246 dwFlags & LOAD_WITH_ALTERED_SEARCH_PATH ? lpLibFileName : NULL);
247
248 RtlInitUnicodeString(&DllName, (LPWSTR)lpLibFileName);
249
250 if (DllName.Buffer[DllName.Length/sizeof(WCHAR) - 1] == L' ')
251 {
252 RtlCreateUnicodeString(&DllName, (LPWSTR)lpLibFileName);
253 while (DllName.Length > sizeof(WCHAR) &&
254 DllName.Buffer[DllName.Length/sizeof(WCHAR) - 1] == L' ')
255 {
256 DllName.Length -= sizeof(WCHAR);
257 }
258 DllName.Buffer[DllName.Length/sizeof(WCHAR)] = UNICODE_NULL;
259 FreeString = TRUE;
260 }
261
262 if (dwFlags & LOAD_LIBRARY_AS_DATAFILE)
263 {
264 Status = LdrGetDllHandle(SearchPath, NULL, &DllName, (PVOID*)&hInst);
265 if (!NT_SUCCESS(Status))
266 {
267 /* The method in load_library_as_datafile allows searching for the
268 * 'native' libraries only
269 */
270 Status = LoadLibraryAsDatafile(SearchPath, DllName.Buffer, &hInst);
271 goto done;
272 }
273 }
274
275 /* HACK!!! FIXME */
276 if (InWindows)
277 {
278 /* Call the API Properly */
279 Status = LdrLoadDll(SearchPath,
280 &DllCharacteristics,
281 &DllName,
282 (PVOID*)&hInst);
283 }
284 else
285 {
286 /* Call the ROS API. NOTE: Don't fix this, I have a patch to merge later. */
287 Status = LdrLoadDll(SearchPath, &dwFlags, &DllName, (PVOID*)&hInst);
288 }
289
290 done:
291 RtlFreeHeap(RtlGetProcessHeap(), 0, SearchPath);
292 if (FreeString)
293 RtlFreeUnicodeString(&DllName);
294 if ( !NT_SUCCESS(Status))
295 {
296 SetLastErrorByStatus (Status);
297 return NULL;
298 }
299
300 return hInst;
301 }
302
303
304 /*
305 * @implemented
306 */
307 FARPROC
308 WINAPI
309 GetProcAddress( HMODULE hModule, LPCSTR lpProcName )
310 {
311 ANSI_STRING ProcedureName;
312 FARPROC fnExp = NULL;
313 NTSTATUS Status;
314
315 if (HIWORD(lpProcName) != 0)
316 {
317 RtlInitAnsiString (&ProcedureName,
318 (LPSTR)lpProcName);
319 Status = LdrGetProcedureAddress ((PVOID)hModule,
320 &ProcedureName,
321 0,
322 (PVOID*)&fnExp);
323 }
324 else
325 {
326 Status = LdrGetProcedureAddress ((PVOID)hModule,
327 NULL,
328 (ULONG)lpProcName,
329 (PVOID*)&fnExp);
330 }
331
332 if (!NT_SUCCESS(Status))
333 {
334 SetLastErrorByStatus(Status);
335 fnExp = NULL;
336 }
337
338 return fnExp;
339 }
340
341
342 /*
343 * @implemented
344 */
345 BOOL WINAPI FreeLibrary(HINSTANCE hLibModule)
346 {
347 NTSTATUS Status;
348
349 if (!hLibModule)
350 {
351 SetLastError(ERROR_INVALID_HANDLE);
352 return FALSE;
353 }
354
355 if ((ULONG_PTR)hLibModule & 1)
356 {
357 /* this is a LOAD_LIBRARY_AS_DATAFILE module */
358 char *ptr = (char *)hLibModule - 1;
359 UnmapViewOfFile(ptr);
360 return TRUE;
361 }
362
363 Status = LdrUnloadDll(hLibModule);
364 if (!NT_SUCCESS(Status))
365 {
366 SetLastErrorByStatus(Status);
367 return FALSE;
368 }
369
370 return TRUE;
371 }
372
373
374 /*
375 * @implemented
376 */
377 VOID
378 WINAPI
379 FreeLibraryAndExitThread (
380 HMODULE hLibModule,
381 DWORD dwExitCode
382 )
383 {
384 FreeLibrary(hLibModule);
385 ExitThread(dwExitCode);
386 }
387
388
389 /*
390 * @implemented
391 */
392 DWORD
393 WINAPI
394 GetModuleFileNameA (
395 HINSTANCE hModule,
396 LPSTR lpFilename,
397 DWORD nSize
398 )
399 {
400 ANSI_STRING FileName;
401 PLIST_ENTRY ModuleListHead;
402 PLIST_ENTRY Entry;
403 PLDR_DATA_TABLE_ENTRY Module;
404 PPEB Peb;
405 ULONG Length = 0;
406
407 Peb = NtCurrentPeb ();
408 RtlEnterCriticalSection (Peb->LoaderLock);
409
410 if (hModule == NULL)
411 hModule = Peb->ImageBaseAddress;
412
413 ModuleListHead = &Peb->Ldr->InLoadOrderModuleList;
414 Entry = ModuleListHead->Flink;
415
416 while (Entry != ModuleListHead)
417 {
418 Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
419 if (Module->DllBase == (PVOID)hModule)
420 {
421 Length = min(nSize, Module->FullDllName.Length / sizeof(WCHAR));
422 FileName.Length = 0;
423 FileName.MaximumLength = (USHORT)Length * sizeof(WCHAR);
424 FileName.Buffer = lpFilename;
425
426 /* convert unicode string to ansi (or oem) */
427 if (bIsFileApiAnsi)
428 RtlUnicodeStringToAnsiString (&FileName,
429 &Module->FullDllName,
430 FALSE);
431 else
432 RtlUnicodeStringToOemString (&FileName,
433 &Module->FullDllName,
434 FALSE);
435
436 if (nSize < Length)
437 SetLastErrorByStatus (STATUS_BUFFER_TOO_SMALL);
438 else
439 lpFilename[Length] = '\0';
440
441 RtlLeaveCriticalSection (Peb->LoaderLock);
442 return Length;
443 }
444
445 Entry = Entry->Flink;
446 }
447
448 SetLastErrorByStatus (STATUS_DLL_NOT_FOUND);
449 RtlLeaveCriticalSection (Peb->LoaderLock);
450
451 return 0;
452 }
453
454
455 /*
456 * @implemented
457 */
458 DWORD
459 WINAPI
460 GetModuleFileNameW (
461 HINSTANCE hModule,
462 LPWSTR lpFilename,
463 DWORD nSize
464 )
465 {
466 UNICODE_STRING FileName;
467 PLIST_ENTRY ModuleListHead;
468 PLIST_ENTRY Entry;
469 PLDR_DATA_TABLE_ENTRY Module;
470 PPEB Peb;
471 ULONG Length = 0;
472
473 Peb = NtCurrentPeb ();
474 RtlEnterCriticalSection (Peb->LoaderLock);
475
476 if (hModule == NULL)
477 hModule = Peb->ImageBaseAddress;
478
479 ModuleListHead = &Peb->Ldr->InLoadOrderModuleList;
480 Entry = ModuleListHead->Flink;
481 while (Entry != ModuleListHead)
482 {
483 Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
484
485 if (Module->DllBase == (PVOID)hModule)
486 {
487 Length = min(nSize, Module->FullDllName.Length / sizeof(WCHAR));
488 FileName.Length = 0;
489 FileName.MaximumLength = (USHORT) Length * sizeof(WCHAR);
490 FileName.Buffer = lpFilename;
491
492 RtlCopyUnicodeString (&FileName,
493 &Module->FullDllName);
494 if (nSize < Length)
495 SetLastErrorByStatus (STATUS_BUFFER_TOO_SMALL);
496 else
497 lpFilename[Length] = L'\0';
498
499 RtlLeaveCriticalSection (Peb->LoaderLock);
500
501 return Length;
502 }
503
504 Entry = Entry->Flink;
505 }
506
507 SetLastErrorByStatus (STATUS_DLL_NOT_FOUND);
508 RtlLeaveCriticalSection (Peb->LoaderLock);
509
510 return 0;
511 }
512
513
514 /*
515 * @implemented
516 */
517 HMODULE
518 WINAPI
519 GetModuleHandleA ( LPCSTR lpModuleName )
520 {
521 ANSI_STRING ModuleName;
522 NTSTATUS Status;
523 PTEB pTeb = NtCurrentTeb();
524
525 if (lpModuleName == NULL)
526 {
527 return ((HMODULE)pTeb->ProcessEnvironmentBlock->ImageBaseAddress);
528 }
529
530 RtlInitAnsiString(&ModuleName, lpModuleName);
531
532 Status = RtlAnsiStringToUnicodeString(&pTeb->StaticUnicodeString,
533 &ModuleName,
534 FALSE);
535
536 if (NT_SUCCESS(Status))
537 {
538 return GetModuleHandleW(pTeb->StaticUnicodeString.Buffer);
539 }
540
541 SetLastErrorByStatus(Status);
542 return FALSE;
543 }
544
545
546 /*
547 * @implemented
548 */
549 HMODULE
550 WINAPI
551 GetModuleHandleW (LPCWSTR lpModuleName)
552 {
553 UNICODE_STRING ModuleName;
554 PVOID BaseAddress;
555 NTSTATUS Status;
556
557 if (lpModuleName == NULL)
558 return ((HMODULE)NtCurrentPeb()->ImageBaseAddress);
559
560 RtlInitUnicodeString (&ModuleName,
561 (LPWSTR)lpModuleName);
562
563 Status = LdrGetDllHandle (0,
564 0,
565 &ModuleName,
566 &BaseAddress);
567 if (!NT_SUCCESS(Status))
568 {
569 SetLastErrorByStatus (Status);
570 return NULL;
571 }
572
573 return ((HMODULE)BaseAddress);
574 }
575
576
577 /*
578 * @implemented
579 */
580 BOOL
581 WINAPI
582 GetModuleHandleExW(IN DWORD dwFlags,
583 IN LPCWSTR lpModuleName OPTIONAL,
584 OUT HMODULE* phModule)
585 {
586 HMODULE hModule;
587 NTSTATUS Status;
588 BOOL Ret = FALSE;
589
590 if (phModule == NULL ||
591 ((dwFlags & (GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT)) ==
592 (GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT)))
593 {
594 SetLastError(ERROR_INVALID_PARAMETER);
595 return FALSE;
596 }
597
598 if (lpModuleName == NULL)
599 {
600 hModule = NtCurrentPeb()->ImageBaseAddress;
601 }
602 else
603 {
604 if (dwFlags & GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS)
605 {
606 hModule = (HMODULE)RtlPcToFileHeader((PVOID)lpModuleName,
607 (PVOID*)&hModule);
608 if (hModule == NULL)
609 {
610 SetLastErrorByStatus(STATUS_DLL_NOT_FOUND);
611 }
612 }
613 else
614 {
615 hModule = GetModuleHandleW(lpModuleName);
616 }
617 }
618
619 if (hModule != NULL)
620 {
621 if (!(dwFlags & GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT))
622 {
623 Status = LdrAddRefDll((dwFlags & GET_MODULE_HANDLE_EX_FLAG_PIN) ? LDR_PIN_MODULE : 0,
624 hModule);
625
626 if (NT_SUCCESS(Status))
627 {
628 Ret = TRUE;
629 }
630 else
631 {
632 SetLastErrorByStatus(Status);
633 hModule = NULL;
634 }
635 }
636 else
637 Ret = TRUE;
638 }
639
640 *phModule = hModule;
641 return Ret;
642 }
643
644 /*
645 * @implemented
646 */
647 BOOL
648 WINAPI
649 GetModuleHandleExA(IN DWORD dwFlags,
650 IN LPCSTR lpModuleName OPTIONAL,
651 OUT HMODULE* phModule)
652 {
653 ANSI_STRING ModuleName;
654 LPCWSTR lpModuleNameW;
655 NTSTATUS Status;
656 BOOL Ret;
657
658 PTEB pTeb = NtCurrentTeb();
659
660 if (dwFlags & GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS)
661 {
662 lpModuleNameW = (LPCWSTR)lpModuleName;
663 }
664 else
665 {
666 RtlInitAnsiString(&ModuleName, lpModuleName);
667
668 Status = RtlAnsiStringToUnicodeString(&pTeb->StaticUnicodeString,
669 &ModuleName,
670 FALSE);
671
672 if (!NT_SUCCESS(Status))
673 {
674 SetLastErrorByStatus(Status);
675 return FALSE;
676 }
677
678 lpModuleNameW = pTeb->StaticUnicodeString.Buffer;
679 }
680
681 Ret = GetModuleHandleExW(dwFlags,
682 lpModuleNameW,
683 phModule);
684
685 return Ret;
686 }
687
688
689 /*
690 * @implemented
691 */
692 DWORD
693 WINAPI
694 LoadModule (
695 LPCSTR lpModuleName,
696 LPVOID lpParameterBlock
697 )
698 {
699 STARTUPINFOA StartupInfo;
700 PROCESS_INFORMATION ProcessInformation;
701 LOADPARMS32 *LoadParams;
702 char FileName[MAX_PATH];
703 char *CommandLine, *t;
704 BYTE Length;
705
706 LoadParams = (LOADPARMS32*)lpParameterBlock;
707 if(!lpModuleName || !LoadParams || (((WORD*)LoadParams->lpCmdShow)[0] != 2))
708 {
709 /* windows doesn't check parameters, we do */
710 SetLastError(ERROR_INVALID_PARAMETER);
711 return 0;
712 }
713
714 if(!SearchPathA(NULL, lpModuleName, ".exe", MAX_PATH, FileName, NULL) &&
715 !SearchPathA(NULL, lpModuleName, NULL, MAX_PATH, FileName, NULL))
716 {
717 return ERROR_FILE_NOT_FOUND;
718 }
719
720 Length = (BYTE)LoadParams->lpCmdLine[0];
721 if(!(CommandLine = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY,
722 strlen(lpModuleName) + Length + 2)))
723 {
724 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
725 return 0;
726 }
727
728 /* Create command line string */
729 strcpy(CommandLine, lpModuleName);
730 t = CommandLine + strlen(CommandLine);
731 *(t++) = ' ';
732 memcpy(t, LoadParams->lpCmdLine + 1, Length);
733
734 /* Build StartupInfo */
735 RtlZeroMemory(&StartupInfo, sizeof(STARTUPINFOA));
736 StartupInfo.cb = sizeof(STARTUPINFOA);
737 StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
738 StartupInfo.wShowWindow = ((WORD*)LoadParams->lpCmdShow)[1];
739
740 if(!CreateProcessA(FileName, CommandLine, NULL, NULL, FALSE, 0, LoadParams->lpEnvAddress,
741 NULL, &StartupInfo, &ProcessInformation))
742 {
743 DWORD Error;
744
745 RtlFreeHeap(RtlGetProcessHeap(), 0, CommandLine);
746 /* return the right value */
747 Error = GetLastError();
748 switch(Error)
749 {
750 case ERROR_BAD_EXE_FORMAT:
751 {
752 return ERROR_BAD_FORMAT;
753 }
754 case ERROR_FILE_NOT_FOUND:
755 case ERROR_PATH_NOT_FOUND:
756 {
757 return Error;
758 }
759 }
760 return 0;
761 }
762
763 RtlFreeHeap(RtlGetProcessHeap(), 0, CommandLine);
764
765 /* Wait up to 15 seconds for the process to become idle */
766 if (NULL != lpfnGlobalRegisterWaitForInputIdle)
767 {
768 lpfnGlobalRegisterWaitForInputIdle(ProcessInformation.hProcess, 15000);
769 }
770
771 NtClose(ProcessInformation.hThread);
772 NtClose(ProcessInformation.hProcess);
773
774 return 33;
775 }
776
777 /* EOF */