source: trunk/kLdr/kLdrDyld.c@ 2836

Last change on this file since 2836 was 2836, checked in by bird, 19 years ago

more prototyping. (And avoid 64-bit div/rem)

  • Property svn:keywords set to Id
File size: 20.5 KB
Line 
1/* $Id: kLdrDyld.c 2836 2006-10-26 03:58:53Z bird $ */
2/** @file
3 *
4 * kLdr - The Dynamic Loader.
5 *
6 * Copyright (c) 2006 knut st. osmundsen <bird@anduin.net>
7 *
8 *
9 * This file is part of kLdr.
10 *
11 * kLdr is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kLdr is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kLdr; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include <kLdr.h>
32#include "kLdrHlp.h"
33#include "kLdrInternal.h"
34
35
36/*******************************************************************************
37* Defined Constants And Macros *
38*******************************************************************************/
39/** @def KLDRDYLD_STRICT
40 * Define KLDRDYLD_STRICT to enabled strict checks in kLdrDyld. */
41#define KLDRDYLD_STRICT 1
42
43/** @def KLDRDYLD_ASSERT
44 * Assert that an expression is true when KLDRDYLD_STRICT is defined.
45 */
46#ifdef KLDRDYLD_STRICT
47# define KLDRDYLD_ASSERT(expr) kldrHlpAssert(expr)
48#else
49# define KLDRDYLD_ASSERT(expr) do {} while (0)
50#endif
51
52
53/*******************************************************************************
54* Global Variables *
55*******************************************************************************/
56/** Pointer to the head module (the executable).
57 * (This is exported, so no prefix.) */
58PKLDRDYLDMOD kLdrDyldHead = NULL;
59/** Pointer to the tail module.
60 * (This is exported, so no prefix.) */
61PKLDRDYLDMOD kLdrDyldTail = NULL;
62/** Pointer to the head module of the termination order list. */
63PKLDRDYLDMOD g_pkLdrDyldTermHead;
64/** Pointer to the tail module of the termination order list. */
65PKLDRDYLDMOD g_pkLdrDyldTermTail;
66/** Pointer to the head module of the bind order list.
67 * The modules in this list makes up the global namespace used when binding symbol unix fashion. */
68PKLDRDYLDMOD g_pkLdrDyldBindHead;
69/** Pointer to the tail module of the bind order list. */
70PKLDRDYLDMOD g_pkLdrDyldBindTail;
71
72/** Stack of modules involved in the active loads.
73 *
74 * The main purpose is to allow module init routines to loading and unloading
75 * modules without upsetting the init order and to assist in dereferencing
76 * modules on load failure.
77 *
78 * Each call to kLdrDyldLoad and kLdrDyldLoadExe will start a load frame
79 * when doing a fresh load. All dependant modules will be pushed on each
80 * reference. The frame is completed when all the dependant modules has
81 * been resolved (or a failure occurs). After doing fixups, the frame is
82 * used to do module initialization. Should an error occur during the load
83 * the frame will be used to dereference all the modules involved in the
84 * load operation (it will not however, be used for termination calls as
85 * they are postponed till the last load operation completes).
86 *
87 * Should any of the init calls load a module, a new frame will be created
88 * for that operation and processed before the init call returns to the
89 * previous frame.
90 */
91PPKLDRDYLDMOD g_pakLdrDyld;
92/** The number of used entries in the g_pakLdrDyld array. */
93uint32_t g_ckLdrDyld;
94/** The number of entries allocated for the g_pakLdrDyld array. */
95uint32_t g_ckLdrDyldAllocated;
96
97/** The global error buffer. */
98char g_szkLdrDyldError[1024];
99
100/** The Library search path. */
101char kLdrDyldLibraryPath[4096];
102/** The executable flags. */
103uint32_t kLdrDyldFlags;
104
105
106/*******************************************************************************
107* Internal Functions *
108*******************************************************************************/
109static int kldrDyldDoLoad(const char *pszDll, const char *pszDefPrefix, const char *pszDefSuffix, KLDRDYLDSEARCH enmSearch,
110 unsigned fFlags, PPKLDRDYLDMOD ppMod, char *pszErr, size_t cchErr);
111static int kldrDyldDoUnload(PKLDRDYLDMOD pMod);
112static int kldrDyldDoFindByName(const char *pszDll, const char *pszDefPrefix, const char *pszDefSuffix, KLDRDYLDSEARCH enmSearch,
113 unsigned fFlags, PPKLDRDYLDMOD ppMod);
114static int kldrDyldDoFindByAddress(uintptr_t Address, PPKLDRDYLDMOD ppMod, uint32_t *piSegment, uintptr_t *poffSegment);
115static int kldrDyldDoGetName(PKLDRDYLDMOD pMod, char *pszName, size_t cchName);
116static int kldrDyldDoGetFilename(PKLDRDYLDMOD pMod, char *pszFilename, size_t cchFilename);
117static int kldrDyldDoQuerySymbol(PKLDRDYLDMOD pMod, uint32_t uSymbolOrdinal, const char *pszSymbolName, uintptr_t *pValue, uint32_t *pfKind);
118static void kldrDyldStartLoading(void);
119static void kldrDyldStopLoading(void);
120static int kldrDyldCopyError(int rc, char *pszErr, size_t cchErr);
121
122
123
124/**
125 * Initialize the dynamic loader.
126 */
127int kldrDyInit(void)
128{
129 kLdrDyldHead = kLdrDyldTail = NULL;
130 g_pkLdrDyldTermHead = g_pkLdrDyldTermTail = NULL;
131 g_pkLdrDyldBindHead = g_pkLdrDyldBindTail = NULL;
132 kLdrDyldFlags = 0;
133 g_szkLdrDyldError[0] = '\0';
134 return 0;
135}
136
137
138/**
139 * Terminate the dynamic loader.
140 */
141void kldrDyTerm(void)
142{
143
144}
145
146
147/**
148 * Loads a module into the current process.
149 *
150 * @returns 0 on success, non-zero native OS status code or kLdr status code on failure.
151 * @param pszDll The name of the dll to open.
152 * @param pszDefPrefix Prefix to use when searching.
153 * @param pszDefSuffix Suffix to use when searching.
154 * @param enmSearch Method to use when locating the module and any modules it may depend on.
155 * @param fFlags Flags, a combintation of the KLDRYDLD_LOAD_FLAGS_* \#defines.
156 * @param phMod Where to store the handle to the loaded module.
157 * @param pszErr Where to store extended error information. (optional)
158 * @param cchErr The size of the buffer pointed to by pszErr.
159 */
160int kLdrDyldLoad(const char *pszDll, const char *pszDefPrefix, const char *pszDefSuffix, KLDRDYLDSEARCH enmSearch,
161 unsigned fFlags, PHKLDRMOD phMod, char *pszErr, size_t cchErr)
162{
163 int rc;
164
165 /* validate arguments and initialize return values. */
166 if (pszErr && cchErr)
167 *pszErr = '\0';
168 *phMod = NIL_HKLDRMOD;
169 KLDRHLP_VALIDATE_STRING(pszDll);
170 KLDRHLP_VALIDATE_OPTIONAL_STRING(pszDefPrefix);
171 KLDRHLP_VALIDATE_OPTIONAL_STRING(pszDefSuffix);
172 KLDRHLP_VALIDATE_ENUM(enmSearch, KLDRDYLD_SEARCH);
173 KLDRHLP_VALIDATE_OPTIONAL_BUFFER(pszErr, cchErr);
174
175 /* get the semaphore and do the job. */
176 rc = kldrHlpSemRequest();
177 if (!rc)
178 {
179 PKLDRDYLDMOD pMod = NULL;
180 rc = kldrDyldDoLoad(pszDll, pszDefPrefix, pszDefSuffix, enmSearch, fFlags, phMod, pszErr, cchErr);
181 kldrHlpSemRelease();
182 *phMod = pMod;
183 }
184 return rc;
185}
186
187
188/**
189 * Unloads a module loaded by kLdrDyldLoad.
190 *
191 * @returns 0 on success, non-zero native OS status code or kLdr status code on failure.
192 * @param hMod Module handle.
193 */
194int kLdrDyldUnload(HKLDRMOD hMod)
195{
196 int rc;
197
198 /* validate */
199 KLDRDYLD_VALIDATE_HKLDRMOD(hMod);
200
201 /* get sem & do work */
202 rc = kldrHlpSemRequest();
203 if (!rc)
204 {
205 rc = kldrDyldDoUnload(hMod);
206 kldrHlpSemRelease();
207 }
208 return rc;
209}
210
211
212/**
213 * Finds a module by name or filename.
214 *
215 * This call does not increase any reference counters and must not be
216 * paired with kLdrDyldUnload() like kLdrDyldLoad().
217 *
218 * @returns 0 on success.
219 * @returns KLDR_ERR_MODULE_NOT_FOUND on failure.
220 * @param pszDll The name of the dll to look for.
221 * @param pszDefPrefix Prefix than can be used when searching.
222 * @param pszDefSuffix Suffix than can be used when searching.
223 * @param enmSearch Method to use when locating the module.
224 * @param fFlags Flags, a combintation of the KLDRYDLD_LOAD_FLAGS_* \#defines.
225 * @param phMod Where to store the handle of the module on success.
226 */
227int kLdrDyldFindByName(const char *pszDll, const char *pszDefPrefix, const char *pszDefSuffix, KLDRDYLDSEARCH enmSearch,
228 unsigned fFlags, PHKLDRMOD phMod)
229{
230 int rc;
231
232 /* validate & initialize */
233 *phMod = NIL_HKLDRMOD;
234 KLDRHLP_VALIDATE_STRING(pszDll);
235
236 /* get sem & do work */
237 rc = kldrHlpSemRequest();
238 if (!rc)
239 {
240 PKLDRDYLDMOD pMod = NULL;
241 rc = kldrDyldDoFindByName(pszDll, pszDefPrefix, pszDefSuffix, enmSearch, fFlags, phMod);
242 kldrHlpSemRelease();
243 *phMod = pMod;
244 }
245 return rc;
246}
247
248
249/**
250 * Finds a module by address.
251 *
252 * This call does not increase any reference counters and must not be
253 * paired with kLdrDyldUnload() like kLdrDyldLoad().
254 *
255 * @returns 0 on success.
256 * @returns KLDR_ERR_MODULE_NOT_FOUND on failure.
257 * @param Address The address believed to be within some module.
258 * @param phMod Where to store the module handle on success.
259 * @param piSegment Where to store the segment number. (optional)
260 * @param poffSegment Where to store the offset into the segment. (optional)
261 */
262int kLdrDyldFindByAddress(uintptr_t Address, PHKLDRMOD phMod, uint32_t *piSegment, uintptr_t *poffSegment)
263{
264 int rc;
265
266 /* validate & initialize */
267 *phMod = NIL_HKLDRMOD;
268 if (piSegment)
269 *piSegment = ~(uint32_t)0;
270 if (poffSegment)
271 *poffSegment = ~(uintptr_t)0;
272
273 /* get sem & do work */
274 rc = kldrHlpSemRequest();
275 if (!rc)
276 {
277 PKLDRDYLDMOD pMod = NULL;
278 rc = kldrDyldDoFindByAddress(Address, &pMod, piSegment, poffSegment);
279 kldrHlpSemRelease();
280 *phMod = pMod;
281 }
282 return rc;
283}
284
285
286/**
287 * Gets the module name.
288 *
289 * @returns 0 on success and pszName filled with the name.
290 * @returns KLDR_ERR_INVALID_HANDLE or KLDR_ERR_BUFFER_OVERFLOW on failure.
291 * @param hMod The module handle.
292 * @param pszName Where to put the name.
293 * @param cchName The size of the name buffer.
294 * @see kLdrDyldGetFilename
295 */
296int kLdrDyldGetName(HKLDRMOD hMod, char *pszName, size_t cchName)
297{
298 int rc;
299
300 /* validate */
301 if (pszName && cchName)
302 *pszName = '\0';
303 KLDRDYLD_VALIDATE_HKLDRMOD(hMod);
304 KLDRHLP_VALIDATE_BUFFER(pszName, cchName);
305
306 /* get sem & do work */
307 rc = kldrHlpSemRequest();
308 if (!rc)
309 {
310 rc = kldrDyldDoGetName(hMod, pszName, cchName);
311 kldrHlpSemRelease();
312 }
313 return rc;
314}
315
316
317/**
318 * Gets the module filename.
319 *
320 * @returns 0 on success and pszFilename filled with the name.
321 * @returns KLDR_ERR_INVALID_HANDLE or KLDR_ERR_BUFFER_OVERFLOW on failure.
322 * @param hMod The module handle.
323 * @param pszFilename Where to put the filename.
324 * @param cchFilename The size of the filename buffer.
325 * @see kLdrDyldGetName
326 */
327int kLdrDyldGetFilename(HKLDRMOD hMod, char *pszFilename, size_t cchFilename)
328{
329 int rc;
330
331 /* validate & initialize */
332 if (pszFilename && cchFilename);
333 *pszFilename = '\0';
334 KLDRDYLD_VALIDATE_HKLDRMOD(hMod);
335 KLDRHLP_VALIDATE_BUFFER(pszFilename, cchFilename);
336
337 /* get sem & do work */
338 rc = kldrHlpSemRequest();
339 if (!rc)
340 {
341 rc = kldrDyldDoGetFilename(hMod, pszFilename, cchFilename);
342 kldrHlpSemRelease();
343 }
344 return rc;
345}
346
347
348/**
349 * Queries the value and type of a symbol.
350 *
351 * @returns 0 on success and pValue and pfKind set.
352 * @returns KLDR_ERR_INVALID_HANDLE or KLDR_ERR_SYMBOL_NOT_FOUND on failure.
353 * @param hMod The module handle.
354 * @param uSymbolOrdinal The symbol ordinal. This is ignored if pszSymbolName is non-zero.
355 * @param pszSymbolName The symbol name.
356 * @param pValue Where to put the symbol value. Optional if pfKind is non-zero.
357 * @param pfKind Where to put the symbol kind flags. Optional if pValue is non-zero.
358 */
359int kLdrDyldQuerySymbol(HKLDRMOD hMod, uint32_t uSymbolOrdinal, const char *pszSymbolName, uintptr_t *pValue, uint32_t *pfKind)
360{
361 int rc;
362
363 /* validate & initialize */
364 if (pfKind)
365 *pfKind = 0;
366 if (pValue)
367 *pValue = 0;
368 if (!pfKind && !pValue)
369 return KLDR_ERR_INVALID_PARAMETER;
370 KLDRDYLD_VALIDATE_HKLDRMOD(hMod);
371 KLDRHLP_VALIDATE_OPTIONAL_STRING(pszSymbolName);
372
373 /* get sem & do work */
374 rc = kldrHlpSemRequest();
375 if (!rc)
376 {
377 rc = kldrDyldDoQuerySymbol(hMod, uSymbolOrdinal, pszSymbolName, pValue, pfKind);
378 kldrHlpSemRelease();
379 }
380 return rc;
381}
382
383
384
385
386
387/**
388 * Worker for kLdrDyldLoad().
389 * @internal
390 */
391static int kldrDyldDoLoad(const char *pszDll, const char *pszDefPrefix, const char *pszDefSuffix, KLDRDYLDSEARCH enmSearch,
392 unsigned fFlags, PPKLDRDYLDMOD ppMod, char *pszErr, size_t cchErr)
393{
394 int rc;
395
396 /*
397 * Try find it among the modules that's already loaded.
398 */
399 rc = kldrDyldFindExistingModule(pszDll, pszDefPrefix, pszDefSuffix, enmSearch, fFlags, ppMod);
400 if (!rc)
401 {
402 /*
403 * If we're in a module termination call we must check that all the modules we
404 * depend on are loaded and initialized.
405 */
406//continue here if ((*ppMod)->enmState::KLDRSTATE_LOADED)
407 {
408 kldrHlpAssert(!"implement me");
409 }
410 return kldrDyldModDynamicLoad(*ppMod);
411 }
412
413 /*
414 * Try open it.
415 */
416 kldrDyldStartLoading();
417 rc = kldrDyldFindNewModule(pszDll, pszDefPrefix, pszDefSuffix, enmSearch, fFlags, ppMod);
418 if (!rc)
419 {
420
421
422 }
423 return kldrDyldCopyError(rc, pszErr, cchErr);
424}
425
426
427/**
428 * Worker for kLdrDyldUnload().
429 * @internal
430 */
431static int kldrDyldDoUnload(PKLDRDYLDMOD pMod)
432{
433 return kldrDyldModDynamicUnload(pMod);
434}
435
436
437/**
438 * Worker for kLdrDyldFindByName().
439 * @internal
440 */
441static int kldrDyldDoFindByName(const char *pszDll, const char *pszDefPrefix, const char *pszDefSuffix, KLDRDYLDSEARCH enmSearch,
442 unsigned fFlags, PPKLDRDYLDMOD ppMod)
443{
444 return kldrDyldFindExistingModule(pszDll, pszDefPrefix, pszDefSuffix, enmSearch, fFlags, ppMod);
445}
446
447
448/**
449 * Worker for kLdrDyldFindByAddress().
450 * @internal
451 */
452static int kldrDyldDoFindByAddress(uintptr_t Address, PPKLDRDYLDMOD ppMod, uint32_t *piSegment, uintptr_t *poffSegment)
453{
454 /* Scan the segments of each module in the load list. */
455 PKLDRDYLDMOD pMod = kLdrDyldHead;
456 while (pMod)
457 {
458 uint32_t iSeg;
459 for (iSeg = 0; iSeg < pMod->pMod->cSegments; iSeg++)
460 {
461 uintmax_t off = (uintmax_t)Address - pMod->pMod->aSegments[iSeg].LoadAddress;
462 if (off < pMod->pMod->aSegments[iSeg].cb)
463 {
464 *ppMod = pMod->hMod;
465 if (piSegment)
466 *piSegment = iSeg;
467 if (poffSegment)
468 *poffSegment = (uintptr_t)off;
469 return 0;
470 }
471 }
472
473 /* next */
474 pMod = pMod->Load.pNext;
475 }
476
477 return KLDR_ERR_MODULE_NOT_FOUND;
478}
479
480
481/**
482 * Worker for kLdrDyldGetName().
483 * @internal
484 */
485static int kldrDyldDoGetName(PKLDRDYLDMOD pMod, char *pszName, size_t cchName)
486{
487 return kldrDyldModGetName(pMod, pszName, cchName);
488}
489
490
491/**
492 * Worker for kLdrDyldGetFilename().
493 * @internal
494 */
495static int kldrDyldDoGetFilename(PKLDRDYLDMOD pMod, char *pszFilename, size_t cchFilename)
496{
497 return kldrDyldModGetFilename(pMod, pszFilename, cchFilename);
498}
499
500
501/**
502 * Worker for kLdrDyldQuerySymbol().
503 * @internal
504 */
505static int kldrDyldDoQuerySymbol(PKLDRDYLDMOD pMod, uint32_t uSymbolOrdinal, const char *pszSymbolName, uintptr_t *pValue, uint32_t *pfKind)
506{
507 return kldrDyldModQuerySymbol(pMod, uSymbolOrdinal, pszSymbolName, pValue, pfKind);
508}
509
510
511#if 0
512void kldrLoadExe(PKLDREXEARGS pArgs)
513{
514 /*
515 * Copy the arguments into the globals and do load init.
516 */
517 kLdrFlags = pArgs->fFlags;
518 kLdrHlpMemCopy(kLdrLibraryPath, pArgs->szLibPath, KLDR_MIN(sizeof(pArgs->szLibPath), sizeof(kLdrLibraryPath)));
519 int rc = kldrInit();
520 if (rc)
521 kldrFailure(rc, "kLdr: Init failure, rc=%d\n", rc);
522
523 /*
524 * Open the executable module.
525 */
526 PKLDRMOD pExe;
527 kldrOpenExe(pArgs->szExecutable, &pExe);
528
529 /* Map the segments. */
530 kldrModMapSegments(pExe);
531
532 /*
533 * This is the point where we switch to the executable
534 * stack, allocating it if necessary.
535 */
536 void *pvBottom;
537 kldrModSetupStack(pExe, &pvBottom);
538 kldrLoadExecSwitchStack(pvBottom);
539}
540
541
542void kldrLoadExeOnNewStack(void)
543{
544 /*
545 * Load all dependant modules.
546 */
547 PKLDRMOD pCur;
548 do for (pCur = kLdrModuleHead; pCur; pCur = pCur->pNext)
549 {
550 if (pCur->enmState >= KLDRSTATE_DEPS)
551 continue;
552 kldrModLoadDeps(pCur);
553 }
554 while (pCur);
555
556 /*
557 * Do fixups (FIFO).
558 */
559 for (pCur = kLdrModuleHead; pCur; pCur = pCur->pNext)
560 {
561 if (pCur->enmState >= KLDRSTATE_FIXED)
562 continue;
563 kldrModFixup(pCur, 0);
564 }
565
566 /*
567 * Do module initialization.
568 */
569 for (pCur = kLdrModuleTail; pCur != kLdrModuleTail; pCur = pCur->pPrev)
570 {
571 if (pCur->enmState >= KLDRSTATE_INITED)
572 continue;
573 kldrModCallInit(pCur);
574 }
575
576 /*
577 * Get the executable start address and commit the work that's been done.
578 */
579 void *pvEntry;
580 kldrModGetExeEntry(&pvEntry);
581
582 for (pCur = kLdrModuleHead; pCur; pCur = pCur->pNext)
583 if (pCur->enmState == KLDRSTATE_INITED)
584 pCur->enmState = KLDRSTATE_LOADED;
585
586 kldrHlpSemRelease();
587
588 /*
589 * We're now ready for starting the executable code.
590 */
591 kldrOSStartExe(pLdrModuleHead, pvEntry);
592}
593
594#endif
595
596/**
597 * Starts loading a new module and its dependencies.
598 */
599static void kldrDyldStartLoading(void)
600{
601#ifdef KLDRDYLD_STRICT
602 /* check that all modules are in the correct state */
603 PKLDRDYLDMOD pMod = kLdrDyldHead;
604 while (pMod)
605 {
606 //KLDRDYLD_ASSERT(pMod->enmState == KLDRSTATE_LOADED);
607
608 /* next */
609 pMod = pMod->Load.pNext;
610 }
611#endif
612}
613
614
615/**
616 * Records the loading of the module.
617 *
618 * @return 0 on success, KLDR_ERR_NO_MEMORY if we can't expand the table.
619 * @param pMod The module to record.
620 */
621static int kldrDyldRecord(PKLDRDYLDMOD pMod)
622{
623
624}
625
626
627
628/**
629 * Done loading a module and its dependencies.
630 *
631 * If the load failed, unload all the modules involved.
632 * If the load succeeded,
633 *
634 * @returns rc.
635 * @param rc The status code.
636 */
637static void kldrDyldDoneLoading(int rc)
638{
639
640}
641
642
643
644/**
645 * Panic / failure
646 *
647 * @returns rc if we're in a position where we can return.
648 * @param rc Return code.
649 * @param pszFormat Message string. Limited fprintf like formatted.
650 * @param ... Message string arguments.
651 */
652int kldrFailure(int rc, const char *pszFormat, ...)
653{
654 kldrHlpExit(1);
655 return rc;
656}
657
658
659/**
660 * Copies the error string to the user buffer.
661 *
662 * @returns rc.
663 * @param rc The status code.
664 * @param pszErr Where to copy the error string to.
665 * @param cchErr The size of the destination buffer.
666 */
667static int kldrDyldCopyError(int rc, char *pszErr, size_t cchErr)
668{
669 size_t cchToCopy;
670
671 /* if no error string, format the rc into a string. */
672 if (!g_szkLdrDyldError[0] && rc)
673 kldrHlpInt2Ascii(g_szkLdrDyldError, sizeof(g_szkLdrDyldError), rc, 10);
674
675 /* copy it if we got something. */
676 if (cchErr && pszErr && g_szkLdrDyldError[0])
677 {
678 cchToCopy = kLdrHlpStrLen(g_szkLdrDyldError);
679 if (cchToCopy >= cchErr)
680 cchToCopy = cchErr - 1;
681 kLdrHlpMemCopy(pszErr, g_szkLdrDyldError, cchToCopy);
682 pszErr[cchToCopy] = '\0';
683 }
684
685 return rc;
686}
687
Note: See TracBrowser for help on using the repository browser.