VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/dbg/dbgcfg.cpp@ 46072

Last change on this file since 46072 was 46071, checked in by vboxsync, 12 years ago

build fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 58.2 KB
Line 
1/* $Id: dbgcfg.cpp 46071 2013-05-14 15:28:37Z vboxsync $ */
2/** @file
3 * IPRT - Debugging Configuration.
4 */
5
6/*
7 * Copyright (C) 2013 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#define LOG_GROUP RTLOGGROUP_DBG
32#include <iprt/dbg.h>
33#include "internal/iprt.h"
34
35#include <iprt/asm.h>
36#include <iprt/assert.h>
37#include <iprt/critsect.h>
38#include <iprt/ctype.h>
39#include <iprt/dir.h>
40#include <iprt/err.h>
41#include <iprt/env.h>
42#include <iprt/file.h>
43#ifdef IPRT_WITH_HTTP
44# include <iprt/http.h>
45#endif
46#include <iprt/list.h>
47#include <iprt/log.h>
48#include <iprt/mem.h>
49#include <iprt/path.h>
50#include <iprt/process.h>
51#include <iprt/semaphore.h>
52#include <iprt/string.h>
53#include <iprt/uuid.h>
54#include "internal/magics.h"
55
56
57/*******************************************************************************
58* Structures and Typedefs *
59*******************************************************************************/
60/**
61 * String list entry.
62 */
63typedef struct RTDBGCFGSTR
64{
65 /** List entry. */
66 RTLISTNODE ListEntry;
67 /** Domain specific flags. */
68 uint16_t fFlags;
69 /** The length of the string. */
70 uint16_t cch;
71 /** The string. */
72 char sz[1];
73} RTDBGCFGSTR;
74/** Pointer to a string list entry. */
75typedef RTDBGCFGSTR *PRTDBGCFGSTR;
76
77
78/**
79 * Log callback.
80 *
81 * @param hDbgCfg The debug config instance.
82 * @param iLevel The message level.
83 * @param pszMsg The message.
84 * @param pvUser User argument.
85 */
86typedef DECLCALLBACK(int) FNRTDBGCFGLOG(RTDBGCFG hDbgCfg, uint32_t iLevel, const char *pszMsg, void *pvUser);
87/** Pointer to a log callback. */
88typedef FNRTDBGCFGLOG *PFNRTDBGCFGLOG;
89
90/**
91 * Configuration instance.
92 */
93typedef struct RTDBGCFGINT
94{
95 /** The magic value (RTDBGCFG_MAGIC). */
96 uint32_t u32Magic;
97 /** Reference counter. */
98 uint32_t volatile cRefs;
99 /** Flags, see RTDBGCFG_FLAGS_XXX. */
100 uint64_t fFlags;
101
102 /** List of paths to search for debug files and executable images. */
103 RTLISTANCHOR PathList;
104 /** List of debug file suffixes. */
105 RTLISTANCHOR SuffixList;
106 /** List of paths to search for source files. */
107 RTLISTANCHOR SrcPathList;
108
109#ifdef RT_OS_WINDOWS
110 /** The _NT_ALT_SYMBOL_PATH and _NT_SYMBOL_PATH combined. */
111 RTLISTANCHOR NtSymbolPathList;
112 /** The _NT_EXECUTABLE_PATH. */
113 RTLISTANCHOR NtExecutablePathList;
114 /** The _NT_SOURCE_PATH. */
115 RTLISTANCHOR NtSourcePath;
116#endif
117
118 /** Log callback function. */
119 PFNRTDBGCFGLOG pfnLogCallback;
120 /** User argument to pass to the log callback. */
121 void *pvLogUser;
122
123 /** Critical section protecting the instance data. */
124 RTCRITSECTRW CritSect;
125} *PRTDBGCFGINT;
126
127/**
128 * Mnemonics map entry for a 64-bit unsigned property value.
129 */
130typedef struct RTDBGCFGU64MNEMONIC
131{
132 /** The flags to set or clear. */
133 uint64_t fFlags;
134 /** The mnemonic. */
135 const char *pszMnemonic;
136 /** The length of the mnemonic. */
137 uint8_t cchMnemonic;
138 /** If @c true, the bits in fFlags will be set, if @c false they will be
139 * cleared. */
140 bool fSet;
141} RTDBGCFGU64MNEMONIC;
142/** Pointer to a read only mnemonic map entry for a uint64_t property. */
143typedef RTDBGCFGU64MNEMONIC const *PCRTDBGCFGU64MNEMONIC;
144
145
146/** @name Open flags.
147 * @{ */
148/** The operative system mask. The values are RT_OPSYS_XXX. */
149#define RTDBGCFG_O_OPSYS_MASK UINT32_C(0x000000ff)
150/** The files may be compressed MS styled. */
151#define RTDBGCFG_O_MAYBE_COMPRESSED_MS RT_BIT_32(26)
152/** Whether to make a recursive search. */
153#define RTDBGCFG_O_RECURSIVE RT_BIT_32(27)
154/** We're looking for a separate debug file. */
155#define RTDBGCFG_O_EXT_DEBUG_FILE RT_BIT_32(28)
156/** We're looking for an executable image. */
157#define RTDBGCFG_O_EXECUTABLE_IMAGE RT_BIT_32(29)
158/** The file search should be done in an case insensitive fashion. */
159#define RTDBGCFG_O_CASE_INSENSITIVE RT_BIT_32(30)
160/** Use Windbg style symbol servers when encountered in the path. */
161#define RTDBGCFG_O_SYMSRV RT_BIT_32(31)
162/** @} */
163
164
165/*******************************************************************************
166* Defined Constants And Macros *
167*******************************************************************************/
168/** Validates a debug module handle and returns rc if not valid. */
169#define RTDBGCFG_VALID_RETURN_RC(pThis, rc) \
170 do { \
171 AssertPtrReturn((pThis), (rc)); \
172 AssertReturn((pThis)->u32Magic == RTDBGCFG_MAGIC, (rc)); \
173 AssertReturn((pThis)->cRefs > 0, (rc)); \
174 } while (0)
175
176
177/*******************************************************************************
178* Global Variables *
179*******************************************************************************/
180/** Mnemonics map for RTDBGCFGPROP_FLAGS. */
181static const RTDBGCFGU64MNEMONIC g_aDbgCfgFlags[] =
182{
183 { RTDBGCFG_FLAGS_DEFERRED, RT_STR_TUPLE("deferred"), true },
184 { RTDBGCFG_FLAGS_DEFERRED, RT_STR_TUPLE("nodeferred"), false },
185 { RTDBGCFG_FLAGS_NO_SYM_SRV, RT_STR_TUPLE("symsrv"), false },
186 { RTDBGCFG_FLAGS_NO_SYM_SRV, RT_STR_TUPLE("nosymsrv"), true },
187 { RTDBGCFG_FLAGS_NO_SYSTEM_PATHS, RT_STR_TUPLE("syspaths"), false },
188 { RTDBGCFG_FLAGS_NO_SYSTEM_PATHS, RT_STR_TUPLE("nosyspaths"), true },
189 { RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH, RT_STR_TUPLE("rec"), false },
190 { RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH, RT_STR_TUPLE("norec"), true },
191 { RTDBGCFG_FLAGS_NO_RECURSIV_SRC_SEARCH, RT_STR_TUPLE("recsrc"), false },
192 { RTDBGCFG_FLAGS_NO_RECURSIV_SRC_SEARCH, RT_STR_TUPLE("norecsrc"), true },
193 { 0, NULL, 0, false }
194};
195
196
197
198/**
199 * Runtime logging, level 1.
200 *
201 * @param pThis The debug config instance data.
202 * @param pszFormat The message format string.
203 * @param ... Arguments references in the format string.
204 */
205static void rtDbgCfgLog1(PRTDBGCFGINT pThis, const char *pszFormat, ...)
206{
207 if (LogIsEnabled() || (pThis && pThis->pfnLogCallback))
208 {
209 va_list va;
210 va_start(va, pszFormat);
211 char *pszMsg = RTStrAPrintf2V(pszFormat, va);
212 va_end(va);
213
214 Log(("RTDbgCfg: %s", pszMsg));
215 if (pThis && pThis->pfnLogCallback)
216 pThis->pfnLogCallback(pThis, 1, pszMsg, pThis->pvLogUser);
217 RTStrFree(pszMsg);
218 }
219}
220
221
222/**
223 * Runtime logging, level 2.
224 *
225 * @param pThis The debug config instance data.
226 * @param pszFormat The message format string.
227 * @param ... Arguments references in the format string.
228 */
229static void rtDbgCfgLog2(PRTDBGCFGINT pThis, const char *pszFormat, ...)
230{
231 if (LogIs2Enabled() || (pThis && pThis->pfnLogCallback))
232 {
233 va_list va;
234 va_start(va, pszFormat);
235 char *pszMsg = RTStrAPrintf2V(pszFormat, va);
236 va_end(va);
237
238 Log(("RTDbgCfg: %s", pszMsg));
239 if (pThis && pThis->pfnLogCallback)
240 pThis->pfnLogCallback(pThis, 2, pszMsg, pThis->pvLogUser);
241 RTStrFree(pszMsg);
242 }
243}
244
245
246/**
247 * Checks if the file system at the given path is case insensitive or not.
248 *
249 * @returns true / false
250 * @param pszPath The path to query about.
251 */
252static int rtDbgCfgIsFsCaseInsensitive(const char *pszPath)
253{
254 RTFSPROPERTIES Props;
255 int rc = RTFsQueryProperties(pszPath, &Props);
256 if (RT_FAILURE(rc))
257 return RT_OPSYS == RT_OPSYS_DARWIN
258 || RT_OPSYS == RT_OPSYS_DOS
259 || RT_OPSYS == RT_OPSYS_OS2
260 || RT_OPSYS == RT_OPSYS_NT
261 || RT_OPSYS == RT_OPSYS_WINDOWS;
262 return !Props.fCaseSensitive;
263}
264
265
266/**
267 * Worker that does case sensitive file/dir searching.
268 *
269 * @returns true / false.
270 * @param pszPath The path buffer containing an existing directory.
271 * RTPATH_MAX in size. On success, this will contain
272 * the combined path with @a pszName case correct.
273 * @param offLastComp The offset of the last component (for chopping it
274 * off).
275 * @param pszName What we're looking for.
276 * @param enmType What kind of thing we're looking for.
277 */
278static bool rtDbgCfgIsXxxxAndFixCaseWorker(char *pszPath, size_t offLastComp, const char *pszName,
279 RTDIRENTRYTYPE enmType)
280{
281 /** @todo IPRT should generalize this so we can use host specific tricks to
282 * speed it up. */
283
284 /* Return straight away if the name isn't case foldable. */
285 if (!RTStrIsCaseFoldable(pszName))
286 return false;
287
288 /*
289 * Try some simple case folding games.
290 */
291 RTStrToLower(&pszPath[offLastComp]);
292 if (RTFileExists(pszPath))
293 return true;
294
295 RTStrToUpper(&pszPath[offLastComp]);
296 if (RTFileExists(pszPath))
297 return true;
298
299 /*
300 * Open the directory and check each entry in it.
301 */
302 pszPath[offLastComp] = '\0';
303 PRTDIR pDir;
304 int rc = RTDirOpen(&pDir, pszPath);
305 if (RT_FAILURE(rc))
306 return false;
307
308 for (;;)
309 {
310 /* Read the next entry. */
311 union
312 {
313 RTDIRENTRY Entry;
314 uint8_t ab[_4K];
315 } u;
316 size_t cbBuf = sizeof(u);
317 rc = RTDirRead(pDir, &u.Entry, &cbBuf);
318 if (RT_FAILURE(rc))
319 break;
320
321 if ( !RTStrICmp(pszName, u.Entry.szName)
322 && ( u.Entry.enmType == enmType
323 || u.Entry.enmType == RTDIRENTRYTYPE_UNKNOWN
324 || u.Entry.enmType == RTDIRENTRYTYPE_SYMLINK) )
325 {
326 pszPath[offLastComp] = '\0';
327 rc = RTPathAppend(pszPath, RTPATH_MAX, u.Entry.szName);
328 if ( u.Entry.enmType != enmType
329 && RT_SUCCESS(rc))
330 RTDirQueryUnknownType(pszPath, true /*fFollowSymlinks*/, &u.Entry.enmType);
331
332 if ( u.Entry.enmType == enmType
333 || RT_FAILURE(rc))
334 {
335 RTDirClose(pDir);
336 if (RT_FAILURE(rc))
337 {
338 pszPath[offLastComp] = '\0';
339 return false;
340 }
341 return true;
342 }
343 }
344 }
345
346 RTDirClose(pDir);
347 pszPath[offLastComp] = '\0';
348
349 return false;
350}
351
352
353/**
354 * Appends @a pszSubDir to @a pszPath and check whether it exists and is a
355 * directory.
356 *
357 * If @a fCaseInsensitive is set, we will do a case insensitive search for a
358 * matching sub directory.
359 *
360 * @returns true / false
361 * @param pszPath The path buffer containing an existing
362 * directory. RTPATH_MAX in size.
363 * @param pszSubDir The sub directory to append.
364 * @param fCaseInsensitive Whether case insensitive searching is required.
365 */
366static bool rtDbgCfgIsDirAndFixCase(char *pszPath, const char *pszSubDir, bool fCaseInsensitive)
367{
368 /* Save the length of the input path so we can restore it in the case
369 insensitive branch further down. */
370 size_t const cchPath = strlen(pszPath);
371
372 /*
373 * Append the sub directory and check if we got a hit.
374 */
375 int rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
376 if (RT_FAILURE(rc))
377 return false;
378
379 if (RTDirExists(pszPath))
380 return true;
381
382 /*
383 * Do case insensitive lookup if requested.
384 */
385 if (fCaseInsensitive)
386 return rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, pszSubDir, RTDIRENTRYTYPE_DIRECTORY);
387
388 pszPath[cchPath] = '\0';
389 return false;
390}
391
392
393/**
394 * Appends @a pszFilename to @a pszPath and check whether it exists and is a
395 * directory.
396 *
397 * If @a fCaseInsensitive is set, we will do a case insensitive search for a
398 * matching filename.
399 *
400 * @returns true / false
401 * @param pszPath The path buffer containing an existing
402 * directory. RTPATH_MAX in size.
403 * @param pszFilename The file name to append.
404 * @param fCaseInsensitive Whether case insensitive searching is required.
405 * @param fMsCompressed Whether to look for the MS compressed file name
406 * variant.
407 * @param pfProbablyCompressed This is set to true if a MS compressed
408 * filename variant is returned. Optional.
409 */
410static bool rtDbgCfgIsFileAndFixCase(char *pszPath, const char *pszFilename, bool fCaseInsensitive,
411 bool fMsCompressed, bool *pfProbablyCompressed)
412{
413 /* Save the length of the input path so we can restore it in the case
414 insensitive branch further down. */
415 size_t cchPath = strlen(pszPath);
416 if (pfProbablyCompressed)
417 *pfProbablyCompressed = false;
418
419 /*
420 * Append the filename and check if we got a hit.
421 */
422 int rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
423 if (RT_FAILURE(rc))
424 return false;
425
426 if (RTFileExists(pszPath))
427 return true;
428
429 /*
430 * Do case insensitive file lookup if requested.
431 */
432 if (fCaseInsensitive)
433 {
434 if (rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, pszFilename, RTDIRENTRYTYPE_FILE))
435 return true;
436 }
437
438 /*
439 * Look for MS compressed file if requested.
440 */
441 if ( fMsCompressed
442 && (unsigned char)pszFilename[strlen(pszFilename) - 1] < 0x7f)
443 {
444 pszPath[cchPath] = '\0';
445 rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
446 AssertRCReturn(rc, false);
447 pszPath[strlen(pszPath) - 1] = '_';
448
449 if (pfProbablyCompressed)
450 *pfProbablyCompressed = true;
451
452 if (RTFileExists(pszPath))
453 return true;
454
455 if (fCaseInsensitive)
456 {
457 /* Note! Ugly hack here, the pszName parameter points into pszPath! */
458 if (rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, RTPathFilename(pszPath), RTDIRENTRYTYPE_FILE))
459 return true;
460 }
461
462 if (pfProbablyCompressed)
463 *pfProbablyCompressed = false;
464 }
465
466 pszPath[cchPath] = '\0';
467 return false;
468}
469
470
471static int rtDbgCfgTryOpenDir(PRTDBGCFGINT pThis, char *pszPath, PRTPATHSPLIT pSplitFn, uint32_t fFlags,
472 PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
473{
474 int rcRet = VWRN_NOT_FOUND;
475 int rc2;
476
477 /* If the directory doesn't exist, just quit immediately.
478 Note! Our case insensitivity doesn't extend to the search dirs themselfs,
479 only to the bits under neath them. */
480 if (!RTDirExists(pszPath))
481 {
482 rtDbgCfgLog2(pThis, "Dir does not exist: '%s'\n", pszPath);
483 return rcRet;
484 }
485
486 /* Figure out whether we have to do a case sensitive search or not.
487 Note! As a simplification, we don't ask for case settings in each
488 directory under the user specified path, we assume the file
489 systems that mounted there have compatible settings. Faster
490 that way. */
491 bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
492 && !rtDbgCfgIsFsCaseInsensitive(pszPath);
493
494 size_t const cchPath = strlen(pszPath);
495
496 /*
497 * Look for the file with less and less of the original path given.
498 */
499 for (unsigned i = RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps); i < pSplitFn->cComps; i++)
500 {
501 pszPath[cchPath] = '\0';
502
503 rc2 = VINF_SUCCESS;
504 for (unsigned j = i; j < pSplitFn->cComps - 1U && RT_SUCCESS(rc2); j++)
505 if (!rtDbgCfgIsDirAndFixCase(pszPath, pSplitFn->apszComps[i], fCaseInsensitive))
506 rc2 = VERR_FILE_NOT_FOUND;
507
508 if (RT_SUCCESS(rc2))
509 {
510 if (rtDbgCfgIsFileAndFixCase(pszPath, pSplitFn->apszComps[pSplitFn->cComps - 1], fCaseInsensitive, false, NULL))
511 {
512 rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
513 rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
514 if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
515 {
516 if (rc2 == VINF_CALLBACK_RETURN)
517 rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
518 else
519 rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
520 return rc2;
521 }
522 rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
523 if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
524 rcRet = rc2;
525 }
526 }
527 }
528
529 /*
530 * Do a recursive search if requested.
531 */
532 if ( (fFlags & RTDBGCFG_O_RECURSIVE)
533 && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH) )
534 {
535 /** @todo Recursive searching will be done later. */
536 }
537
538 return rcRet;
539}
540
541static int rtDbgCfgUnpackMsCacheFile(PRTDBGCFGINT pThis, char *pszPath, const char *pszFilename)
542{
543 rtDbgCfgLog2(pThis, "Unpacking '%s'...\n", pszPath);
544
545 /*
546 * Duplicate the source file path, just for simplicity and restore the
547 * final character in the orignal. We cheerfully ignorining any
548 * possibility of multibyte UTF-8 sequences just like the caller did when
549 * setting it to '_'.
550 */
551 char *pszSrcArchive = RTStrDup(pszPath);
552 if (!pszSrcArchive)
553 return VERR_NO_STR_MEMORY;
554
555 pszPath[strlen(pszPath) - 1] = RT_C_TO_LOWER(pszFilename[strlen(pszFilename) - 1]);
556
557
558 /*
559 * Figuring out the argument list for the platform specific unpack util.
560 */
561#ifdef RT_OS_WINDOWS
562 const char *papszArgs[] =
563 {
564 "expand.exe",
565 pszSrcArchive,
566 pszPath,
567 NULL
568 };
569
570#else
571 char szExtractDir[RTPATH_MAX];
572 strcpy(szExtractDir, pszPath);
573 RTPathStripFilename(szExtractDir);
574
575 const char *papszArgs[] =
576 {
577 "cabextract",
578 "-L", /* Lower case extracted files. */
579 "-d", szExtractDir, /* Extraction path */
580 pszSrcArchive,
581 NULL
582 };
583#endif
584
585 /*
586 * Do the unpacking.
587 */
588 RTPROCESS hChild;
589 int rc = RTProcCreate(papszArgs[0], papszArgs, RTENV_DEFAULT,
590#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
591 RTPROC_FLAGS_NO_WINDOW | RTPROC_FLAGS_HIDDEN | RTPROC_FLAGS_SEARCH_PATH,
592#else
593 RTPROC_FLAGS_SEARCH_PATH,
594#endif
595 &hChild);
596 if (RT_SUCCESS(rc))
597 {
598 RTPROCSTATUS ProcStatus;
599 rc = RTProcWait(hChild, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus);
600 if (RT_SUCCESS(rc))
601 {
602 if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
603 && ProcStatus.iStatus == 0)
604 {
605 if (RTPathExists(pszPath))
606 {
607 rtDbgCfgLog1(pThis, "Successfully unpacked '%s' to '%s'.\n", pszSrcArchive, pszPath);
608 rc = VINF_SUCCESS;
609 }
610 else
611 {
612 rtDbgCfgLog1(pThis, "Successfully ran unpacker on '%s', but '%s' is missing!\n", pszSrcArchive, pszPath);
613 rc = VERR_ZIP_ERROR;
614 }
615 }
616 else
617 {
618 rtDbgCfgLog2(pThis, "Unpacking '%s' failed: iStatus=%d enmReason=%d\n",
619 pszSrcArchive, ProcStatus.iStatus, ProcStatus.enmReason);
620 rc = VERR_ZIP_CORRUPTED;
621 }
622 }
623 else
624 rtDbgCfgLog1(pThis, "Error waiting for process: %Rrc\n", rc);
625
626 }
627 else
628 rtDbgCfgLog1(pThis, "Error starting unpack process '%s': %Rrc\n", papszArgs[0], rc);
629
630 return rc;
631}
632
633static int rtDbgCfgTryDownloadAndOpen(PRTDBGCFGINT pThis, const char *pszServer,
634 char *pszPath, const char *pszCacheSubDir, PRTPATHSPLIT pSplitFn,
635 uint32_t fFlags, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
636{
637#ifdef IPRT_WITH_HTTP
638 if (pThis->fFlags & RTDBGCFG_FLAGS_NO_SYM_SRV)
639 return VWRN_NOT_FOUND;
640
641 /*
642 * Create the path.
643 */
644 size_t cchTmp = strlen(pszPath);
645
646 int rc = RTDirCreateFullPath(pszPath, 0766);
647 if (!RTDirExists(pszPath))
648 {
649 Log(("Error creating cache dir '%s': %Rrc\n", pszPath, rc));
650 return rc;
651 }
652
653 const char *pszFilename = pSplitFn->apszComps[pSplitFn->cComps - 1];
654 rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
655 if (RT_FAILURE(rc))
656 return rc;
657 RTStrToLower(&pszPath[cchTmp]);
658 if (!RTDirExists(pszPath))
659 {
660 rc = RTDirCreate(pszPath, 0766, 0);
661 if (RT_FAILURE(rc))
662 {
663 Log(("RTDirCreate(%s) -> %Rrc\n", pszPath, rc));
664 }
665 }
666
667 rc = RTPathAppend(pszPath, RTPATH_MAX, pszCacheSubDir);
668 if (RT_FAILURE(rc))
669 return rc;
670 if (!RTDirExists(pszPath))
671 {
672 rc = RTDirCreate(pszPath, 0766, 0);
673 if (RT_FAILURE(rc))
674 {
675 Log(("RTDirCreate(%s) -> %Rrc\n", pszPath, rc));
676 }
677 }
678
679 /* Prepare the destination file name while we're here. */
680 cchTmp = strlen(pszPath);
681 RTStrToLower(&pszPath[cchTmp]);
682 rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
683 if (RT_FAILURE(rc))
684 return rc;
685
686 /*
687 * Download the file.
688 */
689 RTHTTP hHttp;
690 rc = RTHttpCreate(&hHttp);
691 if (RT_FAILURE(rc))
692 return rc;
693 RTHttpUseSystemProxySettings(hHttp);
694
695 static const char * const s_apszHeaders[] =
696 {
697 "User-Agent: Microsoft-Symbol-Server/6.6.0999.9",
698 "Pragma: no-cache",
699 };
700
701 rc = RTHttpSetHeaders(hHttp, RT_ELEMENTS(s_apszHeaders), s_apszHeaders);
702 if (RT_SUCCESS(rc))
703 {
704 char szUrl[_2K];
705 RTStrPrintf(szUrl, sizeof(szUrl), "%s/%s/%s/%s", pszServer, pszFilename, pszCacheSubDir, pszFilename);
706
707 /** @todo Use some temporary file name and rename it after the operation
708 * since not all systems support read-deny file sharing
709 * settings. */
710 rtDbgCfgLog2(pThis, "Downloading '%s' to '%s'...\n", szUrl, pszPath);
711 rc = RTHttpGetFile(hHttp, szUrl, pszPath);
712 if (RT_FAILURE(rc))
713 {
714 RTFileDelete(pszPath);
715 rtDbgCfgLog1(pThis, "%Rrc on URL '%s'\n", rc, pszPath);
716 }
717 if (rc == VERR_HTTP_NOT_FOUND)
718 {
719 /* Try the compressed version of the file. */
720 pszPath[strlen(pszPath) - 1] = '_';
721 szUrl[strlen(szUrl) - 1] = '_';
722 rtDbgCfgLog2(pThis, "Downloading '%s' to '%s'...\n", szUrl, pszPath);
723 rc = RTHttpGetFile(hHttp, szUrl, pszPath);
724 if (RT_SUCCESS(rc))
725 rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPath, pszFilename);
726 else
727 {
728 rtDbgCfgLog1(pThis, "%Rrc on URL '%s'\n", rc, pszPath);
729 RTFileDelete(pszPath);
730 }
731 }
732 }
733
734 RTHttpDestroy(hHttp);
735
736 /*
737 * If we succeeded, give it a try.
738 */
739 if (RT_SUCCESS(rc))
740 {
741 Assert(RTFileExists(pszPath));
742 rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
743 rc = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
744 if (rc == VINF_CALLBACK_RETURN)
745 rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
746 else if (rc == VERR_CALLBACK_RETURN)
747 rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
748 else
749 rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc, pszPath);
750 }
751
752 return rc;
753
754#else /* !IPRT_WITH_HTTP */
755 return VWRN_NOT_FOUND;
756#endif /* !IPRT_WITH_HTTP */
757}
758
759
760static int rtDbgCfgCopyFileToCache(PRTDBGCFGINT pThis, char const *pszSrc, const char *pchCache, size_t cchCache,
761 const char *pszCacheSubDir, PRTPATHSPLIT pSplitFn)
762{
763 /** @todo copy to cache */
764 return VINF_SUCCESS;
765}
766
767
768static int rtDbgCfgTryOpenCache(PRTDBGCFGINT pThis, char *pszPath, const char *pszCacheSubDir, PRTPATHSPLIT pSplitFn,
769 uint32_t fFlags, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
770{
771 /*
772 * If the cache doesn't exist, fail right away.
773 */
774 if (!pszCacheSubDir || !*pszCacheSubDir)
775 return VWRN_NOT_FOUND;
776 if (!RTDirExists(pszPath))
777 {
778 rtDbgCfgLog2(pThis, "Cache does not exist: '%s'\n", pszPath);
779 return VWRN_NOT_FOUND;
780 }
781
782 size_t cchPath = strlen(pszPath);
783
784 /*
785 * Carefully construct the cache path with case insensitivity in mind.
786 */
787 bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
788 && !rtDbgCfgIsFsCaseInsensitive(pszPath);
789 const char *pszFilename = pSplitFn->apszComps[pSplitFn->cComps - 1];
790
791 if (!rtDbgCfgIsDirAndFixCase(pszPath, pszFilename, fCaseInsensitive))
792 return VWRN_NOT_FOUND;
793
794 if (!rtDbgCfgIsDirAndFixCase(pszPath, pszCacheSubDir, fCaseInsensitive))
795 return VWRN_NOT_FOUND;
796
797 bool fProbablyCompressed = false;
798 if (!rtDbgCfgIsFileAndFixCase(pszPath, pszFilename, fCaseInsensitive,
799 RT_BOOL(fFlags & RTDBGCFG_O_MAYBE_COMPRESSED_MS), &fProbablyCompressed))
800 return VWRN_NOT_FOUND;
801 if (fProbablyCompressed)
802 {
803 int rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPath, pszFilename);
804 if (RT_FAILURE(rc))
805 return VWRN_NOT_FOUND;
806 }
807
808 rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
809 int rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
810 if (rc2 == VINF_CALLBACK_RETURN)
811 rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
812 else if (rc2 == VERR_CALLBACK_RETURN)
813 rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
814 else
815 rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
816 return rc2;
817}
818
819
820static int rtDbgCfgTryOpenList(PRTDBGCFGINT pThis, PRTLISTANCHOR pList, PRTPATHSPLIT pSplitFn, const char *pszCacheSubDir,
821 uint32_t fFlags, char *pszPath, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
822{
823 int rcRet = VWRN_NOT_FOUND;
824 int rc2;
825
826 const char *pchCache = NULL;
827 size_t cchCache = 0;
828 int rcCache = VWRN_NOT_FOUND;
829
830 PRTDBGCFGSTR pCur;
831 RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
832 {
833 size_t cchDir = pCur->cch;
834 const char *pszDir = pCur->sz;
835 rtDbgCfgLog2(pThis, "Path list entry: '%s'\n", pszDir);
836
837 /* This is very simplistic, but we have a unreasonably large path
838 buffer, so it'll work just fine and simplify things greatly below. */
839 if (cchDir >= RTPATH_MAX - 8U)
840 {
841 if (RT_SUCCESS_NP(rcRet))
842 rcRet = VERR_FILENAME_TOO_LONG;
843 continue;
844 }
845
846 /*
847 * Process the path according to it's type.
848 */
849 if (!strncmp(pszDir, RT_STR_TUPLE("srv*")))
850 {
851 /*
852 * Symbol server.
853 */
854 pszDir += sizeof("srv*") - 1;
855 cchDir -= sizeof("srv*") - 1;
856 bool fSearchCache = false;
857 const char *pszServer = (const char *)memchr(pszDir, '*', cchDir);
858 if (!pszServer)
859 pszServer = pszDir;
860 else if (pszServer == pszDir)
861 continue;
862 {
863 fSearchCache = true;
864 pchCache = pszDir;
865 cchCache = pszServer - pszDir;
866 pszServer++;
867 }
868
869 /* We don't have any default cache directory, so skip if the cache is missing. */
870 if (cchCache == 0)
871 continue;
872
873 /* Search the cache first (if we haven't already done so). */
874 if (fSearchCache)
875 {
876 memcpy(pszPath, pchCache, cchCache);
877 pszPath[cchCache] = '\0';
878 rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, pszCacheSubDir, pSplitFn, fFlags,
879 pfnCallback, pvUser1, pvUser2);
880 if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
881 return rc2;
882 }
883
884 /* Try downloading the file. */
885 if (rcCache == VWRN_NOT_FOUND)
886 {
887 memcpy(pszPath, pchCache, cchCache);
888 pszPath[cchCache] = '\0';
889 rc2 = rtDbgCfgTryDownloadAndOpen(pThis, pszServer, pszPath, pszCacheSubDir, pSplitFn, fFlags,
890 pfnCallback, pvUser1, pvUser2);
891 if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
892 return rc2;
893 }
894 }
895 else if (!strncmp(pszDir, RT_STR_TUPLE("cache*")))
896 {
897 /*
898 * Cache directory.
899 */
900 pszDir += sizeof("cache*") - 1;
901 cchDir -= sizeof("cache*") - 1;
902 if (!cchDir)
903 continue;
904 pchCache = pszDir;
905 cchCache = cchDir;
906
907 memcpy(pszPath, pchCache, cchCache);
908 pszPath[cchCache] = '\0';
909 rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, pszCacheSubDir, pSplitFn, fFlags,
910 pfnCallback, pvUser1, pvUser2);
911 if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
912 return rc2;
913 }
914 else
915 {
916 /*
917 * Normal directory. Check for our own 'rec*' and 'norec*' prefix
918 * flags governing recursive searching.
919 */
920 uint32_t fFlagsDir = fFlags;
921 if (!strncmp(pszDir, RT_STR_TUPLE("rec*")))
922 {
923 pszDir += sizeof("rec*") - 1;
924 cchDir -= sizeof("rec*") - 1;
925 fFlagsDir |= RTDBGCFG_O_RECURSIVE;
926 }
927 else if (!strncmp(pszDir, RT_STR_TUPLE("norec*")))
928 {
929 pszDir += sizeof("norec*") - 1;
930 cchDir -= sizeof("norec*") - 1;
931 fFlagsDir &= ~RTDBGCFG_O_RECURSIVE;
932 }
933
934 /* Copy the path into the buffer and do the searching. */
935 memcpy(pszPath, pszDir, cchDir);
936 pszPath[cchDir] = '\0';
937
938 rc2 = rtDbgCfgTryOpenDir(pThis, pszPath, pSplitFn, fFlagsDir, pfnCallback, pvUser1, pvUser2);
939 if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
940 {
941 if ( rc2 == VINF_CALLBACK_RETURN
942 && cchCache > 0)
943 rtDbgCfgCopyFileToCache(pThis, pszPath, pchCache, cchCache, pszCacheSubDir, pSplitFn);
944 return rc2;
945 }
946 }
947
948 /* Propagate errors. */
949 if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
950 rcRet = rc2;
951 }
952
953 return rcRet;
954}
955
956
957/**
958 * Common worker routine for Image and debug info opening.
959 *
960 * This will not search using for suffixes.
961 *
962 * @returns IPRT status code.
963 * @param hDbgCfg The debugging configuration handle. NIL_RTDBGCFG is
964 * accepted, but the result is that no paths will be
965 * searched beyond the given and the current directory.
966 * @param pszFilename The filename to search for. This may or may not
967 * include a full or partial path.
968 * @param pszCacheSubDir The cache subdirectory to look in.
969 * @param fFlags Flags and hints.
970 * @param pfnCallback The open callback routine.
971 * @param pvUser1 User parameter 1.
972 * @param pvUser2 User parameter 2.
973 */
974static int rtDbgCfgOpenWithSubDir(RTDBGCFG hDbgCfg, const char *pszFilename, const char *pszCacheSubDir,
975 uint32_t fFlags, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
976{
977 int rcRet = VINF_SUCCESS;
978 int rc2;
979
980 /*
981 * Do a little validating first.
982 */
983 PRTDBGCFGINT pThis = hDbgCfg;
984 if (pThis != NIL_RTDBGCFG)
985 RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
986 else
987 pThis = NULL;
988 AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
989 AssertPtrReturn(pszCacheSubDir, VERR_INVALID_POINTER);
990 AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
991
992 /*
993 * Do some guessing as to the way we should parse the filename and whether
994 * it's case exact or not.
995 */
996 bool fDosPath = strchr(pszFilename, ':') != NULL
997 || strchr(pszFilename, '\\') != NULL
998 || RT_OPSYS_USES_DOS_PATHS(fFlags & RTDBGCFG_O_OPSYS_MASK)
999 || (fFlags & RTDBGCFG_O_CASE_INSENSITIVE);
1000 if (fDosPath)
1001 fFlags |= RTDBGCFG_O_CASE_INSENSITIVE;
1002
1003 rtDbgCfgLog2(pThis, "Looking for '%s' w/ cache subdir '%s' and %#x flags...\n", pszFilename, pszCacheSubDir, fFlags);
1004
1005 PRTPATHSPLIT pSplitFn;
1006 rc2 = RTPathSplitA(pszFilename, &pSplitFn, fDosPath ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
1007 if (RT_FAILURE(rc2))
1008 return rc2;
1009
1010 /*
1011 * Try the stored file name first if it has a kind of absolute path.
1012 */
1013 char szPath[RTPATH_MAX];
1014 if (RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps))
1015 {
1016 rc2 = RTPathSplitReassemble(pSplitFn, RTPATH_STR_F_STYLE_HOST, szPath, sizeof(szPath));
1017 if (RT_SUCCESS(rc2) && RTFileExists(szPath))
1018 rc2 = pfnCallback(pThis, pszFilename, pvUser1, pvUser2);
1019 }
1020 if ( rc2 != VINF_CALLBACK_RETURN
1021 && rc2 != VERR_CALLBACK_RETURN)
1022 {
1023 /*
1024 * Try the current directory (will take cover relative paths
1025 * skipped above).
1026 */
1027 rc2 = RTPathGetCurrent(szPath, sizeof(szPath));
1028 if (RT_FAILURE(rc2))
1029 strcpy(szPath, ".");
1030 rc2 = rtDbgCfgTryOpenDir(pThis, szPath, pSplitFn, fFlags, pfnCallback, pvUser1, pvUser2);
1031 if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
1032 rcRet = rc2;
1033
1034 if ( rc2 != VINF_CALLBACK_RETURN
1035 && rc2 != VERR_CALLBACK_RETURN
1036 && pThis)
1037 {
1038 rc2 = RTCritSectRwEnterShared(&pThis->CritSect);
1039 if (RT_SUCCESS(rc2))
1040 {
1041 /*
1042 * Run the applicable lists.
1043 */
1044 rc2 = rtDbgCfgTryOpenList(pThis, &pThis->PathList, pSplitFn, pszCacheSubDir, fFlags, szPath,
1045 pfnCallback, pvUser1, pvUser2);
1046 if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
1047 rcRet = rc2;
1048
1049#ifdef RT_OS_WINDOWS
1050 if ( rc2 != VINF_CALLBACK_RETURN
1051 && rc2 != VERR_CALLBACK_RETURN
1052 && (fFlags & RTDBGCFG_O_EXECUTABLE_IMAGE)
1053 && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_SYSTEM_PATHS) )
1054 {
1055 rc2 = rtDbgCfgTryOpenList(pThis, &pThis->NtExecutablePathList, pSplitFn, pszCacheSubDir, fFlags, szPath,
1056 pfnCallback, pvUser1, pvUser2);
1057 if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
1058 rcRet = rc2;
1059 }
1060
1061 if ( rc2 != VINF_CALLBACK_RETURN
1062 && rc2 != VERR_CALLBACK_RETURN
1063 && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_SYSTEM_PATHS) )
1064 {
1065 rc2 = rtDbgCfgTryOpenList(pThis, &pThis->NtSymbolPathList, pSplitFn, pszCacheSubDir, fFlags, szPath,
1066 pfnCallback, pvUser1, pvUser2);
1067 if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
1068 rcRet = rc2;
1069 }
1070#endif
1071 RTCritSectRwLeaveShared(&pThis->CritSect);
1072 }
1073 else if (RT_SUCCESS(rcRet))
1074 rcRet = rc2;
1075 }
1076 }
1077
1078 RTPathSplitFree(pSplitFn);
1079 if ( rc2 == VINF_CALLBACK_RETURN
1080 || rc2 == VERR_CALLBACK_RETURN)
1081 rcRet = rc2;
1082 else if (RT_SUCCESS(rcRet))
1083 rcRet = VERR_NOT_FOUND;
1084 return rcRet;
1085}
1086
1087
1088RTDECL(int) RTDbgCfgOpenPeImage(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp,
1089 PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
1090{
1091 char szSubDir[32];
1092 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
1093 return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
1094 RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
1095 | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXECUTABLE_IMAGE,
1096 pfnCallback, pvUser1, pvUser2);
1097}
1098
1099
1100RTDECL(int) RTDbgCfgOpenPdb70(RTDBGCFG hDbgCfg, const char *pszFilename, PCRTUUID pUuid, uint32_t uAge,
1101 PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
1102{
1103 char szSubDir[64];
1104 if (!pUuid)
1105 szSubDir[0] = '\0';
1106 else
1107 {
1108 /* Stringify the UUID and remove the dashes. */
1109 int rc2 = RTUuidToStr(pUuid, szSubDir, sizeof(szSubDir));
1110 AssertRCReturn(rc2, rc2);
1111
1112 char *pszSrc = szSubDir;
1113 char *pszDst = szSubDir;
1114 char ch;
1115 while ((ch = *pszSrc++))
1116 if (ch != '-')
1117 *pszDst++ = RT_C_TO_UPPER(ch);
1118
1119 RTStrPrintf(pszDst, &szSubDir[sizeof(szSubDir)] - pszDst, "%X", uAge);
1120 }
1121
1122 return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
1123 RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
1124 | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
1125 pfnCallback, pvUser1, pvUser2);
1126}
1127
1128
1129RTDECL(int) RTDbgCfgOpenPdb20(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp, uint32_t uAge,
1130 PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
1131{
1132 /** @todo test this! */
1133 char szSubDir[32];
1134 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, uAge);
1135 return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
1136 RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
1137 | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
1138 pfnCallback, pvUser1, pvUser2);
1139}
1140
1141
1142RTDECL(int) RTDbgCfgOpenDbg(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp,
1143 PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
1144{
1145 char szSubDir[32];
1146 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
1147 return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
1148 RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
1149 | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
1150 pfnCallback, pvUser1, pvUser2);
1151}
1152
1153
1154RTDECL(int) RTDbgCfgOpenDwo(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t uCrc32,
1155 PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
1156{
1157 char szSubDir[32];
1158 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08x", uCrc32);
1159 return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
1160 RT_OPSYS_UNKNOWN | RTDBGCFG_O_EXT_DEBUG_FILE,
1161 pfnCallback, pvUser1, pvUser2);
1162}
1163
1164
1165
1166
1167/**
1168 * Frees a string list.
1169 *
1170 * @param pList The list to free.
1171 */
1172static void rtDbgCfgFreeStrList(PRTLISTANCHOR pList)
1173{
1174 PRTDBGCFGSTR pCur;
1175 PRTDBGCFGSTR pNext;
1176 RTListForEachSafe(pList, pCur, pNext, RTDBGCFGSTR, ListEntry)
1177 {
1178 RTListNodeRemove(&pCur->ListEntry);
1179 RTMemFree(pCur);
1180 }
1181}
1182
1183
1184/**
1185 * Make changes to a string list, given a semicolon separated input string.
1186 *
1187 * @returns VINF_SUCCESS, VERR_FILENAME_TOO_LONG, VERR_NO_MEMORY
1188 * @param pThis The config instance.
1189 * @param enmOp The change operation.
1190 * @param pszValue The input strings separated by semicolon.
1191 * @param fPaths Indicates that this is a path list and that we
1192 * should look for srv and cache prefixes.
1193 * @param pList The string list anchor.
1194 */
1195static int rtDbgCfgChangeStringList(PRTDBGCFGINT pThis, RTDBGCFGOP enmOp, const char *pszValue, bool fPaths,
1196 PRTLISTANCHOR pList)
1197{
1198 if (enmOp == RTDBGCFGOP_SET)
1199 rtDbgCfgFreeStrList(pList);
1200
1201 while (*pszValue)
1202 {
1203 /* Skip separators. */
1204 while (*pszValue == ';')
1205 pszValue++;
1206 if (!*pszValue)
1207 break;
1208
1209 /* Find the end of this path. */
1210 const char *pchPath = pszValue++;
1211 char ch;
1212 while ((ch = *pszValue) && ch != ';')
1213 pszValue++;
1214 size_t cchPath = pszValue - pchPath;
1215 if (cchPath >= UINT16_MAX)
1216 return VERR_FILENAME_TOO_LONG;
1217
1218 if (enmOp == RTDBGCFGOP_REMOVE)
1219 {
1220 /*
1221 * Remove all occurences.
1222 */
1223 PRTDBGCFGSTR pCur;
1224 PRTDBGCFGSTR pNext;
1225 RTListForEachSafe(pList, pCur, pNext, RTDBGCFGSTR, ListEntry)
1226 {
1227 if ( pCur->cch == cchPath
1228 && !memcmp(pCur->sz, pchPath, cchPath))
1229 {
1230 RTListNodeRemove(&pCur->ListEntry);
1231 RTMemFree(pCur);
1232 }
1233 }
1234 }
1235 else
1236 {
1237 /*
1238 * We're adding a new one.
1239 */
1240 PRTDBGCFGSTR pNew = (PRTDBGCFGSTR)RTMemAlloc(RT_OFFSETOF(RTDBGCFGSTR, sz[cchPath + 1]));
1241 if (!pNew)
1242 return VERR_NO_MEMORY;
1243 pNew->cch = (uint16_t)cchPath;
1244 pNew->fFlags = 0;
1245 memcpy(pNew->sz, pchPath, cchPath);
1246 pNew->sz[cchPath] = '\0';
1247
1248 if (enmOp == RTDBGCFGOP_PREPEND)
1249 RTListPrepend(pList, &pNew->ListEntry);
1250 else
1251 RTListAppend(pList, &pNew->ListEntry);
1252 }
1253 }
1254
1255 return VINF_SUCCESS;
1256}
1257
1258
1259/**
1260 * Make changes to a 64-bit value
1261 *
1262 * @returns VINF_SUCCESS, VERR_DBG_CFG_INVALID_VALUE.
1263 * @param pThis The config instance.
1264 * @param enmOp The change operation.
1265 * @param pszValue The input value.
1266 * @param pszMnemonics The mnemonics map for this value.
1267 * @param puValue The value to change.
1268 */
1269static int rtDbgCfgChangeStringU64(PRTDBGCFGINT pThis, RTDBGCFGOP enmOp, const char *pszValue,
1270 PCRTDBGCFGU64MNEMONIC paMnemonics, uint64_t *puValue)
1271{
1272 uint64_t uNew = enmOp == RTDBGCFGOP_SET ? 0 : *puValue;
1273
1274 char ch;
1275 while ((ch = *pszValue))
1276 {
1277 /* skip whitespace and separators */
1278 while (RT_C_IS_SPACE(ch) || RT_C_IS_CNTRL(ch) || ch == ';' || ch == ':')
1279 ch = *++pszValue;
1280 if (!ch)
1281 break;
1282
1283 if (RT_C_IS_DIGIT(ch))
1284 {
1285 uint64_t uTmp;
1286 int rc = RTStrToUInt64Ex(pszValue, (char **)&pszValue, 0, &uTmp);
1287 if (RT_FAILURE(rc) || rc == VWRN_NUMBER_TOO_BIG)
1288 return VERR_DBG_CFG_INVALID_VALUE;
1289
1290 if (enmOp != RTDBGCFGOP_REMOVE)
1291 uNew |= uTmp;
1292 else
1293 uNew &= ~uTmp;
1294 }
1295 else
1296 {
1297 /* A mnemonic, find the end of it. */
1298 const char *pszMnemonic = pszValue - 1;
1299 do
1300 ch = *++pszValue;
1301 while (ch && !RT_C_IS_SPACE(ch) && !RT_C_IS_CNTRL(ch) && ch != ';' && ch != ':');
1302 size_t cchMnemonic = pszValue - pszMnemonic;
1303
1304 /* Look it up in the map and apply it. */
1305 unsigned i = 0;
1306 while (paMnemonics[i].pszMnemonic)
1307 {
1308 if ( cchMnemonic == paMnemonics[i].cchMnemonic
1309 && !memcmp(pszMnemonic, paMnemonics[i].pszMnemonic, cchMnemonic))
1310 {
1311 if (paMnemonics[i].fSet ? enmOp != RTDBGCFGOP_REMOVE : enmOp == RTDBGCFGOP_REMOVE)
1312 uNew |= paMnemonics[i].fFlags;
1313 else
1314 uNew &= ~paMnemonics[i].fFlags;
1315 break;
1316 }
1317 i++;
1318 }
1319
1320 if (!paMnemonics[i].pszMnemonic)
1321 return VERR_DBG_CFG_INVALID_VALUE;
1322 }
1323 }
1324
1325 *puValue = uNew;
1326 return VINF_SUCCESS;
1327}
1328
1329
1330RTDECL(int) RTDbgCfgChangeString(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, RTDBGCFGOP enmOp, const char *pszValue)
1331{
1332 PRTDBGCFGINT pThis = hDbgCfg;
1333 RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
1334 AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
1335 AssertReturn(enmOp > RTDBGCFGOP_INVALID && enmOp < RTDBGCFGOP_END, VERR_INVALID_PARAMETER);
1336 if (!pszValue)
1337 pszValue = "";
1338 else
1339 AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
1340
1341 int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
1342 if (RT_SUCCESS(rc))
1343 {
1344 switch (enmProp)
1345 {
1346 case RTDBGCFGPROP_FLAGS:
1347 rc = rtDbgCfgChangeStringU64(pThis, enmOp, pszValue, g_aDbgCfgFlags, &pThis->fFlags);
1348 break;
1349 case RTDBGCFGPROP_PATH:
1350 rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, true, &pThis->PathList);
1351 break;
1352 case RTDBGCFGPROP_SUFFIXES:
1353 rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, false, &pThis->SuffixList);
1354 break;
1355 case RTDBGCFGPROP_SRC_PATH:
1356 rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, true, &pThis->SrcPathList);
1357 break;
1358 default:
1359 AssertFailed();
1360 rc = VERR_INTERNAL_ERROR_3;
1361 }
1362
1363 RTCritSectRwLeaveExcl(&pThis->CritSect);
1364 }
1365
1366 return rc;
1367}
1368
1369
1370RTDECL(int) RTDbgCfgChangeUInt(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, RTDBGCFGOP enmOp, uint64_t uValue)
1371{
1372 PRTDBGCFGINT pThis = hDbgCfg;
1373 RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
1374 AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
1375 AssertReturn(enmOp > RTDBGCFGOP_INVALID && enmOp < RTDBGCFGOP_END, VERR_INVALID_PARAMETER);
1376
1377 int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
1378 if (RT_SUCCESS(rc))
1379 {
1380 uint64_t *puValue = NULL;
1381 switch (enmProp)
1382 {
1383 case RTDBGCFGPROP_FLAGS:
1384 puValue = &pThis->fFlags;
1385 break;
1386 default:
1387 rc = VERR_DBG_CFG_NOT_UINT_PROP;
1388 }
1389 if (RT_SUCCESS(rc))
1390 {
1391 switch (enmOp)
1392 {
1393 case RTDBGCFGOP_SET:
1394 *puValue = uValue;
1395 break;
1396 case RTDBGCFGOP_APPEND:
1397 case RTDBGCFGOP_PREPEND:
1398 *puValue |= uValue;
1399 break;
1400 case RTDBGCFGOP_REMOVE:
1401 *puValue &= ~uValue;
1402 break;
1403 default:
1404 AssertFailed();
1405 rc = VERR_INTERNAL_ERROR_2;
1406 }
1407 }
1408
1409 RTCritSectRwLeaveExcl(&pThis->CritSect);
1410 }
1411
1412 return rc;
1413}
1414
1415
1416/**
1417 * Querys a string list as a single string (semicolon separators).
1418 *
1419 * @returns VINF_SUCCESS, VERR_BUFFER_OVERFLOW.
1420 * @param pThis The config instance.
1421 * @param pList The string list anchor.
1422 * @param pszValue The output buffer.
1423 * @param cbValue The size of the output buffer.
1424 */
1425static int rtDbgCfgQueryStringList(RTDBGCFG hDbgCfg, PRTLISTANCHOR pList,
1426 char *pszValue, size_t cbValue)
1427{
1428 /*
1429 * Check the length first.
1430 */
1431 size_t cbReq = 1;
1432 PRTDBGCFGSTR pCur;
1433 RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
1434 cbReq += pCur->cch + 1;
1435 if (cbReq > cbValue)
1436 return VERR_BUFFER_OVERFLOW;
1437
1438 /*
1439 * Construct the string list in the buffer.
1440 */
1441 char *psz = pszValue;
1442 RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
1443 {
1444 if (psz != pszValue)
1445 *psz++ = ';';
1446 memcpy(psz, pCur->sz, pCur->cch);
1447 psz += pCur->cch;
1448 }
1449 *psz = '\0';
1450
1451 return VINF_SUCCESS;
1452}
1453
1454
1455/**
1456 * Querys the string value of a 64-bit unsigned int.
1457 *
1458 * @returns VINF_SUCCESS, VERR_BUFFER_OVERFLOW.
1459 * @param pThis The config instance.
1460 * @param uValue The value to query.
1461 * @param pszMnemonics The mnemonics map for this value.
1462 * @param pszValue The output buffer.
1463 * @param cbValue The size of the output buffer.
1464 */
1465static int rtDbgCfgQueryStringU64(RTDBGCFG hDbgCfg, uint64_t uValue, PCRTDBGCFGU64MNEMONIC paMnemonics,
1466 char *pszValue, size_t cbValue)
1467{
1468 /*
1469 * If no mnemonics, just return the hex value.
1470 */
1471 if (!paMnemonics || paMnemonics[0].pszMnemonic)
1472 {
1473 char szTmp[64];
1474 size_t cch = RTStrPrintf(szTmp, sizeof(szTmp), "%#x", uValue);
1475 if (cch + 1 > cbValue)
1476 return VERR_BUFFER_OVERFLOW;
1477 memcpy(pszValue, szTmp, cbValue);
1478 return VINF_SUCCESS;
1479 }
1480
1481 /*
1482 * Check that there is sufficient buffer space first.
1483 */
1484 size_t cbReq = 1;
1485 for (unsigned i = 0; paMnemonics[i].pszMnemonic; i++)
1486 if ( paMnemonics[i].fSet
1487 ? (paMnemonics[i].fFlags & uValue)
1488 : !(paMnemonics[i].fFlags & uValue))
1489 cbReq += (cbReq != 1) + paMnemonics[i].cchMnemonic;
1490 if (cbReq > cbValue)
1491 return VERR_BUFFER_OVERFLOW;
1492
1493 /*
1494 * Construct the string.
1495 */
1496 char *psz = pszValue;
1497 for (unsigned i = 0; paMnemonics[i].pszMnemonic; i++)
1498 if ( paMnemonics[i].fSet
1499 ? (paMnemonics[i].fFlags & uValue)
1500 : !(paMnemonics[i].fFlags & uValue))
1501 {
1502 if (psz != pszValue)
1503 *psz++ = ' ';
1504 memcpy(psz, paMnemonics[i].pszMnemonic, paMnemonics[i].cchMnemonic);
1505 psz += paMnemonics[i].cchMnemonic;
1506 }
1507 *psz = '\0';
1508 return VINF_SUCCESS;
1509}
1510
1511
1512RTDECL(int) RTDbgCfgQueryString(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, char *pszValue, size_t cbValue)
1513{
1514 PRTDBGCFGINT pThis = hDbgCfg;
1515 RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
1516 AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
1517 AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
1518
1519 int rc = RTCritSectRwEnterShared(&pThis->CritSect);
1520 if (RT_SUCCESS(rc))
1521 {
1522 switch (enmProp)
1523 {
1524 case RTDBGCFGPROP_FLAGS:
1525 rc = rtDbgCfgQueryStringU64(pThis, pThis->fFlags, g_aDbgCfgFlags, pszValue, cbValue);
1526 break;
1527 case RTDBGCFGPROP_PATH:
1528 rc = rtDbgCfgQueryStringList(pThis, &pThis->PathList, pszValue, cbValue);
1529 break;
1530 case RTDBGCFGPROP_SUFFIXES:
1531 rc = rtDbgCfgQueryStringList(pThis, &pThis->SuffixList, pszValue, cbValue);
1532 break;
1533 case RTDBGCFGPROP_SRC_PATH:
1534 rc = rtDbgCfgQueryStringList(pThis, &pThis->SrcPathList, pszValue, cbValue);
1535 break;
1536 default:
1537 AssertFailed();
1538 rc = VERR_INTERNAL_ERROR_3;
1539 }
1540
1541 RTCritSectRwLeaveShared(&pThis->CritSect);
1542 }
1543
1544 return rc;
1545}
1546
1547
1548RTDECL(int) RTDbgCfgQueryUInt(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, uint64_t *puValue)
1549{
1550 PRTDBGCFGINT pThis = hDbgCfg;
1551 RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
1552 AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
1553 AssertPtrReturn(puValue, VERR_INVALID_POINTER);
1554
1555 int rc = RTCritSectRwEnterShared(&pThis->CritSect);
1556 if (RT_SUCCESS(rc))
1557 {
1558 switch (enmProp)
1559 {
1560 case RTDBGCFGPROP_FLAGS:
1561 *puValue = pThis->fFlags;
1562 break;
1563 default:
1564 rc = VERR_DBG_CFG_NOT_UINT_PROP;
1565 }
1566
1567 RTCritSectRwLeaveShared(&pThis->CritSect);
1568 }
1569
1570 return rc;
1571}
1572
1573RTDECL(uint32_t) RTDbgCfgRetain(RTDBGCFG hDbgCfg)
1574{
1575 PRTDBGCFGINT pThis = hDbgCfg;
1576 RTDBGCFG_VALID_RETURN_RC(pThis, UINT32_MAX);
1577
1578 uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
1579 Assert(cRefs < UINT32_MAX / 2);
1580 return cRefs;
1581}
1582
1583
1584RTDECL(uint32_t) RTDbgCfgRelease(RTDBGCFG hDbgCfg)
1585{
1586 if (hDbgCfg == NIL_RTDBGCFG)
1587 return 0;
1588
1589 PRTDBGCFGINT pThis = hDbgCfg;
1590 RTDBGCFG_VALID_RETURN_RC(pThis, UINT32_MAX);
1591
1592 uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
1593 if (!cRefs)
1594 {
1595 /*
1596 * Last reference - free all memory.
1597 */
1598 ASMAtomicWriteU32(&pThis->u32Magic, ~RTDBGCFG_MAGIC);
1599 rtDbgCfgFreeStrList(&pThis->PathList);
1600 rtDbgCfgFreeStrList(&pThis->SuffixList);
1601 rtDbgCfgFreeStrList(&pThis->SrcPathList);
1602#ifdef RT_OS_WINDOWS
1603 rtDbgCfgFreeStrList(&pThis->NtSymbolPathList);
1604 rtDbgCfgFreeStrList(&pThis->NtExecutablePathList);
1605 rtDbgCfgFreeStrList(&pThis->NtSourcePath);
1606#endif
1607 RTCritSectRwDelete(&pThis->CritSect);
1608 RTMemFree(pThis);
1609 }
1610 else
1611 Assert(cRefs < UINT32_MAX / 2);
1612 return cRefs;
1613}
1614
1615
1616RTDECL(int) RTDbgCfgCreate(PRTDBGCFG phDbgCfg, const char *pszEnvVarPrefix, bool fNativePaths)
1617{
1618 /*
1619 * Validate input.
1620 */
1621 AssertPtrReturn(phDbgCfg, VERR_INVALID_POINTER);
1622 if (pszEnvVarPrefix)
1623 {
1624 AssertPtrReturn(pszEnvVarPrefix, VERR_INVALID_POINTER);
1625 AssertReturn(*pszEnvVarPrefix, VERR_INVALID_PARAMETER);
1626 }
1627
1628 /*
1629 * Allocate and initialize a new instance.
1630 */
1631 PRTDBGCFGINT pThis = (PRTDBGCFGINT)RTMemAllocZ(sizeof(*pThis));
1632 if (!pThis)
1633 return VERR_NO_MEMORY;
1634
1635 pThis->u32Magic = RTDBGCFG_MAGIC;
1636 pThis->cRefs = 1;
1637 RTListInit(&pThis->PathList);
1638 RTListInit(&pThis->SuffixList);
1639 RTListInit(&pThis->SrcPathList);
1640#ifdef RT_OS_WINDOWS
1641 RTListInit(&pThis->NtSymbolPathList);
1642 RTListInit(&pThis->NtExecutablePathList);
1643 RTListInit(&pThis->NtSourcePath);
1644#endif
1645
1646 int rc = RTCritSectRwInit(&pThis->CritSect);
1647 if (RT_FAILURE(rc))
1648 {
1649 RTMemFree(pThis);
1650 return rc;
1651 }
1652
1653 /*
1654 * Read configurtion from the environment if requested to do so.
1655 */
1656 if (pszEnvVarPrefix || fNativePaths)
1657 {
1658 const size_t cbEnvVar = 256;
1659 const size_t cbEnvVal = 65536 - cbEnvVar;
1660 char *pszEnvVar = (char *)RTMemTmpAlloc(cbEnvVar + cbEnvVal);
1661 if (pszEnvVar)
1662 {
1663 char *pszEnvVal = pszEnvVar + cbEnvVar;
1664
1665 if (pszEnvVarPrefix)
1666 {
1667 static struct
1668 {
1669 RTDBGCFGPROP enmProp;
1670 const char *pszVar;
1671 } const s_aProps[] =
1672 {
1673 { RTDBGCFGPROP_FLAGS, "FLAGS" },
1674 { RTDBGCFGPROP_PATH, "PATH" },
1675 { RTDBGCFGPROP_SUFFIXES, "SUFFIXES" },
1676 { RTDBGCFGPROP_SRC_PATH, "SRC_PATH" },
1677 };
1678
1679 for (unsigned i = 0; i < RT_ELEMENTS(s_aProps); i++)
1680 {
1681 size_t cchEnvVar = RTStrPrintf(pszEnvVar, cbEnvVar, "%s_%s", pszEnvVarPrefix, s_aProps[i].pszVar);
1682 if (cchEnvVar >= cbEnvVar - 1)
1683 {
1684 rc = VERR_BUFFER_OVERFLOW;
1685 break;
1686 }
1687
1688 rc = RTEnvGetEx(RTENV_DEFAULT, pszEnvVar, pszEnvVal, cbEnvVal, NULL);
1689 if (RT_SUCCESS(rc))
1690 {
1691 rc = RTDbgCfgChangeString(pThis, s_aProps[i].enmProp, RTDBGCFGOP_SET, pszEnvVal);
1692 if (RT_FAILURE(rc))
1693 break;
1694 }
1695 else if (rc != VERR_ENV_VAR_NOT_FOUND)
1696 break;
1697 else
1698 rc = VINF_SUCCESS;
1699 }
1700 }
1701
1702 /*
1703 * Pick up system specific search paths.
1704 */
1705 if (RT_SUCCESS(rc) && fNativePaths)
1706 {
1707 struct
1708 {
1709 PRTLISTANCHOR pList;
1710 const char *pszVar;
1711 char chSep;
1712 } aNativePaths[] =
1713 {
1714#ifdef RT_OS_WINDOWS
1715 { &pThis->NtExecutablePathList, "_NT_EXECUTABLE_PATH", ';' },
1716 { &pThis->NtSymbolPathList, "_NT_ALT_SYMBOL_PATH", ';' },
1717 { &pThis->NtSymbolPathList, "_NT_SYMBOL_PATH", ';' },
1718 { &pThis->NtSourcePath, "_NT_SOURCE_PATH", ';' },
1719#endif
1720 { NULL, NULL, 0 }
1721 };
1722 for (unsigned i = 0; aNativePaths[i].pList; i++)
1723 {
1724 Assert(aNativePaths[i].chSep == ';'); /* fix when needed */
1725 rc = RTEnvGetEx(RTENV_DEFAULT, aNativePaths[i].pszVar, pszEnvVal, cbEnvVal, NULL);
1726 if (RT_SUCCESS(rc))
1727 {
1728 rc = rtDbgCfgChangeStringList(pThis, RTDBGCFGOP_APPEND, pszEnvVal, true, aNativePaths[i].pList);
1729 if (RT_FAILURE(rc))
1730 break;
1731 }
1732 else if (rc != VERR_ENV_VAR_NOT_FOUND)
1733 break;
1734 else
1735 rc = VINF_SUCCESS;
1736 }
1737 }
1738 RTMemTmpFree(pszEnvVar);
1739 }
1740 else
1741 rc = VERR_NO_TMP_MEMORY;
1742 if (RT_FAILURE(rc))
1743 {
1744 /*
1745 * Error, bail out.
1746 */
1747 RTDbgCfgRelease(pThis);
1748 return rc;
1749 }
1750 }
1751
1752 /*
1753 * Returns successfully.
1754 */
1755 *phDbgCfg = pThis;
1756
1757 return VINF_SUCCESS;
1758}
1759
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette