void SG_password__forget_all(SG_context *pCtx)
{
	SG_string* pstrFilter = NULL;
	WCHAR* pwszFilter = NULL;
	DWORD numCreds = 0;
	PCREDENTIAL* paCreds = NULL;
	SG_uint32 i;

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pstrFilter, KEY_PREFIX)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrFilter, KEY_SEPARATOR)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrFilter, "*")  );
	SG_ERR_CHECK(  SG_utf8__extern_to_os_buffer__wchar(pCtx, SG_string__sz(pstrFilter), &pwszFilter, NULL)  );

	if ( !CredEnumerateW( pwszFilter, 0, &numCreds, &paCreds) )
	{
		DWORD err = GetLastError();
		if ( ERROR_NOT_FOUND != err)
			SG_ERR_THROW2( SG_ERR_GETLASTERROR(err), (pCtx, "%s", "unable to enumerate saved credentials") );
	}

	for (i = 0; i < numCreds; i++)
	{
		if ( !CredDeleteW( paCreds[i]->TargetName, CRED_TYPE_GENERIC, 0) )
			SG_ERR_THROW2( SG_ERR_GETLASTERROR(GetLastError()), (pCtx, "%s", "unable to delete credential") );
	}

	/* fall through */
fail:
	SG_NULLFREE(pCtx, pwszFilter);
	SG_STRING_NULLFREE(pCtx, pstrFilter);
}
Пример #2
0
void SG_dagfrag_debug__dump(SG_context * pCtx,
							SG_dagfrag * pFrag,
							const char * szLabel,
							SG_uint32 indent,
							SG_string * pStringOutput)
{
	char buf[4000];
	struct _dump_data dump_data;
	dump_data.indent = indent+4;
	dump_data.nrDigits = 6;
	dump_data.pStringOutput = pStringOutput;

	SG_ERR_CHECK_RETURN(  SG_sprintf(pCtx, buf,SG_NrElements(buf),"%*cDagFrag[%p] [%s]\n",indent,' ',pFrag,szLabel)  );
	SG_ERR_CHECK_RETURN(  SG_string__append__sz(pCtx, pStringOutput,buf)  );

	dump_data.stateWanted = SG_DFS_START_MEMBER;
	SG_ERR_CHECK_RETURN(  SG_sprintf(pCtx, buf,SG_NrElements(buf),"%*cStartMembers:\n",indent+2,' ')  );
	SG_ERR_CHECK_RETURN(  SG_string__append__sz(pCtx, pStringOutput,buf)  );
	SG_ERR_CHECK_RETURN(  SG_rbtree__foreach(pCtx, pFrag->m_pRB_Cache,_dump_cb,&dump_data)  );

	dump_data.stateWanted = SG_DFS_INTERIOR_MEMBER;
	SG_ERR_CHECK_RETURN(  SG_sprintf(pCtx,buf,SG_NrElements(buf),"%*cInteriorMembers:\n",indent+2,' ')  );
	SG_ERR_CHECK_RETURN(  SG_string__append__sz(pCtx,pStringOutput,buf)  );
	SG_ERR_CHECK_RETURN(  SG_rbtree__foreach(pCtx,pFrag->m_pRB_Cache,_dump_cb,&dump_data)  );

	dump_data.stateWanted = SG_DFS_END_FRINGE;
	SG_ERR_CHECK_RETURN(  SG_sprintf(pCtx,buf,SG_NrElements(buf),"%*cEndFringe:\n",indent+2,' ')  );
	SG_ERR_CHECK_RETURN(  SG_string__append__sz(pCtx,pStringOutput,buf)  );
	SG_ERR_CHECK_RETURN(  SG_rbtree__foreach(pCtx,pFrag->m_pRB_Cache,_dump_cb,&dump_data)  );
}
Пример #3
0
void SG_getopt__print_option(SG_context* pCtx, SG_console_stream cs, SG_getopt_option* opt, const char * overrideDesc)
{
	SG_string *opts;

	if (opt == NULL)
    {
		SG_ERR_IGNORE(  SG_console(pCtx, cs, "?")  );
		return;
    }

	SG_ERR_CHECK(  SG_string__alloc(pCtx, &opts)  );

	/* We have a valid option which may or may not have a "short
		name" (a single-character alias for the long option). */
	if (opt->optch <= 255)
		SG_ERR_CHECK(  SG_string__sprintf(pCtx, opts, "-%c [--%s]", opt->optch, opt->pStringName)  );
	else
		SG_ERR_CHECK(  SG_string__sprintf(pCtx, opts, "--%s", opt->pStringName)  );

	if (opt->has_arg)
		SG_ERR_CHECK(  SG_string__append__sz(pCtx, opts, " ARG")  );

	if (overrideDesc)
		SG_ERR_IGNORE(  SG_console(pCtx, cs, "%-20s :  %s\n", SG_string__sz(opts), overrideDesc)  );
	else
		SG_ERR_IGNORE(  SG_console(pCtx, cs, "%-20s :  %s\n", SG_string__sz(opts), opt->pStringDescription)  );


fail:
	SG_STRING_NULLFREE(pCtx, opts);
}
static void _get_key(SG_context* pCtx, const char* szRepoSpec, const char* szUserName, SG_string** ppstrKey)
{
	SG_string* pstrKey = NULL;
	SG_string* pstrHostname = NULL;

	SG_ERR_CHECK(  _get_host(pCtx, szRepoSpec, &pstrHostname)  );

	SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pstrKey, KEY_PREFIX KEY_SEPARATOR)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrKey, SG_string__sz(pstrHostname))  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrKey, KEY_SEPARATOR)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrKey, szUserName)  );

	*ppstrKey = pstrKey;
	pstrKey = NULL;

	/* fall through */
fail:
	SG_STRING_NULLFREE(pCtx, pstrKey);
	SG_STRING_NULLFREE(pCtx, pstrHostname);
}
Пример #5
0
static void my_dump_id(SG_context * pCtx,
					   const char* psz_hid,
					   SG_uint32 nrDigits,
					   SG_uint32 indent,
					   SG_string * pStringOutput)
{
	char buf[4000];

	// we can abbreviate the full IDs.

	nrDigits = SG_MIN(nrDigits, SG_HID_MAX_BUFFER_LENGTH);
	nrDigits = SG_MAX(nrDigits, 4);

	// create:
	//     Dagnode[addr]: <child_id> <gen> [<parent_id>...]

	SG_ERR_CHECK_RETURN(  SG_sprintf(pCtx,buf,SG_NrElements(buf),"%*c ",indent,' ')  );
	SG_ERR_CHECK_RETURN(  SG_string__append__sz(pCtx,pStringOutput,buf)  );
	SG_ERR_CHECK_RETURN(  SG_string__append__buf_len(pCtx,pStringOutput,(SG_byte *)(psz_hid),nrDigits)  );
	SG_ERR_CHECK_RETURN(  SG_string__append__sz(pCtx,pStringOutput,"\n")  );
}
static void _get_sync_url(SG_context* pCtx, const char* pszBaseUrl, const char* pszUrlSuffix, const char* pszPushId, const char* pszQueryString, char** ppszUrl)
{
    SG_string* pstrUrl = NULL;

    SG_ERR_CHECK(  SG_STRING__ALLOC__RESERVE(pCtx, &pstrUrl, 1024)  );
    SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrUrl, pszBaseUrl)  );

    if (pszUrlSuffix)
        SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrUrl, pszUrlSuffix)  );

    if (pszPushId)
    {
        SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrUrl, "/")  );
        SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrUrl, pszPushId)  );
    }
    if (pszQueryString)
    {
        SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrUrl, "?")  );
        SG_ERR_CHECK(  SG_string__append__sz(pCtx, pstrUrl, pszQueryString)  );
    }

    SG_ERR_CHECK(  SG_string__sizzle(pCtx, &pstrUrl, (SG_byte**)ppszUrl, NULL)  );

    return;

fail:
    SG_STRING_NULLFREE(pCtx, pstrUrl);
}
void SG_validate__sanitize(
    SG_context* pCtx,
    const char* szValue,
    SG_uint32   uMin,
    SG_uint32   uMax,
    const char* szInvalids,
    SG_uint32   uFixFlags,
    const char* szReplace,
    const char* szAdd,
    SG_string** ppSanitized
)
{
    SG_string* sSanitized = NULL;

    SG_NULLARGCHECK(ppSanitized);

    // treat NULL replacement string as empty
    if (szReplace == NULL)
    {
        szReplace = "";
    }

    // allocate our result string
    SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &sSanitized, szValue)  );

    // if we need to sanitize bad characters, do that
    // Note: We do this first because sanitizing characters might change the
    //       length of the string and affect the min/max length check.
    if (uFixFlags & SG_VALIDATE__RESULT__INVALID_CHARACTER)
    {
        SG_ERR_CHECK(  _replace_chars_with_string(pCtx, sSanitized, szInvalids, szReplace)  );
    }
    if (uFixFlags & SG_VALIDATE__RESULT__CONTROL_CHARACTER)
    {
        SG_ERR_CHECK(  _replace_chars_with_string(pCtx, sSanitized, SG_VALIDATE__CHARS__CONTROL, szReplace)  );
    }

    // if we need to lengthen the string, do that
    // Note: We do this prior to checking the max length because we have more fine
    //       grained control over reducing length than we do over expanding it.  We
    //       can remove individual characters, but only add characters in blocks of
    //       strlen(szAdd).  If uMin and uMax are close to each other, then adding
    //       a single szAdd might take us over uMax.  If that happens, we want to
    //       be able to trim that back down to uMax afterward.
    if (uFixFlags & SG_VALIDATE__RESULT__TOO_SHORT)
    {
        SG_uint32 uSanitized = 0u;
        SG_uint32 uAdd       = 0u;

        SG_ARGCHECK(szAdd != NULL && SG_STRLEN(szAdd) > 0u, szAdd);

        // get the length of both strings
        SG_ERR_CHECK(  SG_utf8__length_in_characters__sz(pCtx, SG_string__sz(sSanitized), &uSanitized)  );
        SG_ERR_CHECK(  SG_utf8__length_in_characters__sz(pCtx, szAdd, &uAdd)  );

        // keep adding until the sanitized string is long enough
        while (uSanitized < uMin)
        {
            SG_ERR_CHECK(  SG_string__append__sz(pCtx, sSanitized, szAdd)  );
            uSanitized += uAdd;
        }
    }

    // if we need to shorten the string, do that
    if (uFixFlags & SG_VALIDATE__RESULT__TOO_LONG)
    {
        SG_ERR_CHECK(  _truncate_string(pCtx, sSanitized, uMax)  );
    }

    // return the sanitized result
    *ppSanitized = sSanitized;
    sSanitized = NULL;

fail:
    SG_STRING_NULLFREE(pCtx, sSanitized);
    return;
}
void SG_cmd_util__get_username_for_repo(
	SG_context *pCtx,
	const char *szRepoName,
	char **ppUsername
	)
{
	SG_string * pUsername = NULL;
	SG_repo * pRepo = NULL;
	char * psz_username = NULL;
	SG_curl * pCurl = NULL;
	SG_string * pUri = NULL;
	SG_string * pResponse = NULL;
	SG_int32 responseStatusCode = 0;
	SG_vhash * pRepoInfo = NULL;
	char * psz_userid = NULL;
	SG_varray * pUsers = NULL;

	SG_NULLARGCHECK_RETURN(ppUsername);

	if(!szRepoName)
	{
		// Look up username based on 'whoami' of repo associated with cwd.

		SG_ERR_IGNORE(  SG_cmd_util__get_repo_from_cwd(pCtx, &pRepo, NULL)  );
		if(pRepo)
			SG_ERR_IGNORE(  SG_user__get_username_for_repo(pCtx, pRepo, &psz_username)  );
		SG_REPO_NULLFREE(pCtx, pRepo);
	}
	else if(SG_sz__starts_with(szRepoName, "http://") || SG_sz__starts_with(szRepoName, "https://"))
	{
		// Look up username based on 'whoami' of admin id of remote repo.

		SG_ERR_CHECK(  SG_curl__alloc(pCtx, &pCurl)  );

		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, &pUri, szRepoName)  );
		SG_ERR_CHECK(  SG_string__append__sz(pCtx, pUri, ".json")  );

		SG_ERR_CHECK(  SG_curl__reset(pCtx, pCurl)  );
		SG_ERR_CHECK(  SG_curl__setopt__sz(pCtx, pCurl, CURLOPT_URL, SG_string__sz(pUri))  );

		SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pResponse)  );
		SG_ERR_CHECK(  SG_curl__set__write_string(pCtx, pCurl, pResponse)  );

		SG_ERR_CHECK(  SG_curl__perform(pCtx, pCurl)  );
		SG_ERR_CHECK(  SG_curl__getinfo__int32(pCtx, pCurl, CURLINFO_RESPONSE_CODE, &responseStatusCode)  );

		if(responseStatusCode==200)
		{
			const char * szAdminId = NULL;
			SG_ERR_CHECK(  SG_VHASH__ALLOC__FROM_JSON__STRING(pCtx, &pRepoInfo, pResponse)  );
			SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pRepoInfo, SG_SYNC_REPO_INFO_KEY__ADMIN_ID, &szAdminId)  );

			SG_ERR_CHECK(  SG_string__clear(pCtx, pUri)  );
			SG_ERR_CHECK(  SG_string__append__format(pCtx, pUri, "/admin/%s/whoami/userid", szAdminId)  );
			SG_ERR_IGNORE(  SG_localsettings__get__sz(pCtx, SG_string__sz(pUri), NULL, &psz_userid, NULL)  );

			if(psz_userid)
			{
				// We now have the userid. Look up the username.


				SG_ERR_CHECK(  SG_string__clear(pCtx, pUri)  );
				SG_ERR_CHECK(  SG_string__append__format(pCtx, pUri, "%s/users.json", szRepoName)  );

				SG_ERR_CHECK(  SG_curl__reset(pCtx, pCurl)  );
				SG_ERR_CHECK(  SG_curl__setopt__sz(pCtx, pCurl, CURLOPT_URL, SG_string__sz(pUri))  );

				SG_ERR_CHECK(  SG_string__clear(pCtx, pResponse)  );
				SG_ERR_CHECK(  SG_curl__set__write_string(pCtx, pCurl, pResponse)  );

				SG_ERR_CHECK(  SG_curl__perform(pCtx, pCurl)  );
				SG_ERR_CHECK(  SG_curl__getinfo__int32(pCtx, pCurl, CURLINFO_RESPONSE_CODE, &responseStatusCode)  );

				if(responseStatusCode==200)
				{
					SG_uint32 i, nUsers;
					SG_ERR_CHECK(  SG_VARRAY__ALLOC__FROM_JSON__STRING(pCtx, &pUsers, pResponse)  );
					SG_ERR_CHECK(  SG_varray__count(pCtx, pUsers, &nUsers)  );
					for(i=0; i<nUsers; ++i)
					{
						SG_vhash * pUser = NULL;
						const char * psz_recid = NULL;
						SG_ERR_CHECK(  SG_varray__get__vhash(pCtx, pUsers, i, &pUser)  );
						SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pUser, "recid", &psz_recid)  );
						if(!strcmp(psz_recid, psz_userid))
						{
							const char * psz_name = NULL;
							SG_ERR_CHECK(  SG_vhash__get__sz(pCtx, pUser, "name", &psz_name)  );
							SG_ERR_CHECK(  SG_STRDUP(pCtx, psz_name, &psz_username)  );
							break;
						}
					}
					SG_VARRAY_NULLFREE(pCtx, pUsers);
				}
				
				SG_NULLFREE(pCtx, psz_userid);
			}

			SG_VHASH_NULLFREE(pCtx, pRepoInfo);
		}

		SG_STRING_NULLFREE(pCtx, pResponse);
		SG_STRING_NULLFREE(pCtx, pUri);
		SG_CURL_NULLFREE(pCtx, pCurl);
	}
	else
	{
		// Look up username based on 'whoami' of repo provided.

		SG_ERR_CHECK(  SG_REPO__OPEN_REPO_INSTANCE(pCtx, szRepoName, &pRepo)  );
		SG_ERR_IGNORE(  SG_user__get_username_for_repo(pCtx, pRepo, &psz_username)  );
		SG_REPO_NULLFREE(pCtx, pRepo);
	}

	*ppUsername = psz_username;

	return;
fail:
	SG_STRING_NULLFREE(pCtx, pUsername);
	SG_REPO_NULLFREE(pCtx, pRepo);
	SG_NULLFREE(pCtx, psz_username);
	SG_CURL_NULLFREE(pCtx, pCurl);
	SG_STRING_NULLFREE(pCtx, pUri);
	SG_STRING_NULLFREE(pCtx, pResponse);
	SG_VHASH_NULLFREE(pCtx, pRepoInfo);
	SG_NULLFREE(pCtx, psz_userid);
	SG_VARRAY_NULLFREE(pCtx, pUsers);
}
Пример #9
0
SG_error SG_context__err_to_string(SG_context* pCtx, SG_string** ppErrString)
{
	SG_error err = SG_ERR_OK;
	char szErr[SG_ERROR_BUFFER_SIZE];
	SG_string* pTempStr = NULL;
	SG_uint32 lenRequired;

	SG_ASSERT( pCtx );
//	SG_ASSERT( pCtx->level < SG_CONTEXT_MAX_ERROR_LEVELS );

	if (!ppErrString)
		return SG_ERR_INVALIDARG;

	if (pCtx->level > 0)
	{
		// when in an error-on-error (level > 0), the error string DOES NOT belong to the
		// error context/level that we are in.  The full message is only defined for the
		// original error that triggered things.

		*ppErrString = NULL;
		return SG_ERR_OK;
	}

	SG_error__get_message(pCtx->errValues[pCtx->level], szErr, sizeof(szErr));

	lenRequired = (  strlen(szErr)
					 + strlen(pCtx->szDescription)
					 + pCtx->lenStackTrace
					 + 100);

	// do all of the formatting in an error-on-error context/level
	// so that none of the string allocation/manipulation trashes
	// the current error context.
	//
	// ***DO NOT JUMP OUT OF THIS PUSH..POP BLOCK.***

	SG_context__push_level(pCtx);
	{
		SG_STRING__ALLOC__RESERVE(pCtx,&pTempStr,lenRequired);

		if (SG_IS_OK(pCtx->errValues[pCtx->level]))
		{
			// since we pre-reserved all of the space we require, we
			// don't expect any of the appends to fail.  So we can
			// ignore intermediate errors after each step.  if one of
			// them does have a problem, the subsequent statements will
			// fail because we already have an error at this level.
			// either way, we don't care because we're going to pop the
			// level in a minute anyway.

			if (szErr[0] != 0)
			{
				SG_string__append__sz(pCtx, pTempStr, szErr);
				if (pCtx->szDescription[0] != 0)
					SG_string__append__sz(pCtx, pTempStr, ": ");
				else
					SG_string__append__sz(pCtx, pTempStr, ".");
			}
			if (pCtx->szDescription[0] != 0)
				SG_string__append__sz(pCtx, pTempStr, pCtx->szDescription);
			if (szErr[0] != 0 || pCtx->szDescription[0] != 0)
				SG_string__append__sz(pCtx, pTempStr, "\n");

			SG_string__append__sz(pCtx, pTempStr, pCtx->szStackTrace);

			// if all of the formating works, we return the allocated string.
			// if not, we delete it and return the formatting error.

			if (SG_IS_OK(pCtx->errValues[pCtx->level]))
				*ppErrString = pTempStr;
			else
				SG_STRING_NULLFREE(pCtx, pTempStr);
		}

		err = pCtx->errValues[pCtx->level];		// we return the error value of the allocation/formatting
	}
	SG_context__pop_level(pCtx);

	return err;
}
Пример #10
0
static void _templatize(
	SG_context *pCtx,
	SG_string *content,
	const _request_headers *pRequestHeaders,
	_replacer_cb replacer)
{
	SG_string *instr = NULL;
	const char *next = NULL;
	SG_string *piece = NULL;
	SG_string *replacement = NULL;
	SG_string *inclusion = NULL;
	SG_string *replaceRaw = NULL;

	SG_ERR_CHECK(  SG_STRING__ALLOC__COPY(pCtx, &instr, content)  );
	SG_ERR_CHECK(  SG_string__clear(pCtx, content)  );
	SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &piece)  );
	SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &replacement)  );
	SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &replaceRaw)  );

	next = SG_string__sz(instr);

	while (*next)
	{
		const char *delim = strstr(next, "{{{");

		if (delim == NULL)
		{
			SG_ERR_CHECK(  SG_string__append__sz(pCtx, content, next)  );
			break;
		}
		else
		{
			const char *rest = delim + 3;
			const char *end = strstr(rest, "}}}");

			if (end == NULL)
			{
				SG_ERR_CHECK(  SG_string__append__sz(pCtx, content, next)  );
				break;
			}

			SG_ERR_CHECK(  SG_string__append__buf_len(pCtx, content, (const SG_byte *)next, (delim - next) * sizeof(char))  );

			SG_ERR_CHECK(  SG_string__clear(pCtx, piece)  );
			SG_ERR_CHECK(  SG_string__clear(pCtx, replacement)  );

			SG_ERR_CHECK(  SG_string__append__buf_len(pCtx, piece, (const SG_byte *)rest, (end - rest) * sizeof(char))  );
			SG_ERR_CHECK(  SG_string__append__string(pCtx, replacement, piece)  );

			if (SG_string__sz(piece)[0] == '<')
			{
				SG_string__remove(pCtx, piece, 0, 1);

				SG_ASSERT(inclusion == NULL);
				SG_ERR_CHECK( _read_template_file(pCtx, SG_string__sz(piece), &inclusion, pRequestHeaders, replacer)  );

				SG_STRING_NULLFREE(pCtx, replacement);
				replacement = inclusion;
				inclusion = NULL;
			}
			else
			{
				SG_bool needEncoding = SG_TRUE;

				SG_ERR_CHECK(  replacer(pCtx, pRequestHeaders, piece, replaceRaw)  );

				// no verb-specific replacement? try generics
				if (sgeq(piece, replacement))
				{
					SG_ERR_CHECK(  _default_replacer(pCtx, pRequestHeaders, piece, replaceRaw,  &needEncoding)  );
				}

				if (needEncoding)
					SG_ERR_CHECK(  SG_htmlencode(pCtx, replaceRaw, replacement)  );
				else
					SG_ERR_CHECK(  SG_string__set__string(pCtx, replacement, replaceRaw)  );
			}

			SG_ERR_CHECK(  SG_string__append__string(pCtx, content, replacement)  );

			next = end + 3;
		}
	}

fail:
	SG_STRING_NULLFREE(pCtx, instr);
	SG_STRING_NULLFREE(pCtx, piece);
	SG_STRING_NULLFREE(pCtx, replacement);
	SG_STRING_NULLFREE(pCtx, replaceRaw);
	SG_STRING_NULLFREE(pCtx, inclusion);
}
Пример #11
0
/**
 * Provides each value from a vhash whose name matches a pattern to another callback.
 * Values that are themselves vhashes are recursed into.
 * Each value's name is prefixed such that it's fully-qualified when passed to the callback.
 * Intended for use as a SG_vhash_foreach_callback.
 */
static void provide_matching_values(
	SG_context*       pCtx,        //< Error and context information.
	void*             pCallerData, //< An allocated instance of provide_matching_values__data.
	const SG_vhash*   pHash,       //< The hash that the current value is from.
	const char*       szName,      //< The name of the current value.
	const SG_variant* pValue       //< The current value.
	)
{
	SG_string* pFullName = NULL;
	SG_string* pScopeName = NULL;
	SG_string* pSettingName = NULL;
	SG_uint32 uValueSize = 0u;
	provide_matching_values__data* pData = NULL;

	SG_UNUSED(pHash);

	SG_NULLARGCHECK_RETURN(pCallerData);
	pData = (provide_matching_values__data*) pCallerData;

	// build the full name of this value from the incoming prefix and name
	SG_ERR_CHECK(  SG_STRING__ALLOC__COPY(pCtx, &pFullName, pData->pPrefix)  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pFullName, "/")  );
	SG_ERR_CHECK(  SG_string__append__sz(pCtx, pFullName, szName)  );

	// if this is a vhash, get its size
	if (pValue->type == SG_VARIANT_TYPE_VHASH)
	{
		SG_ERR_CHECK(  SG_vhash__count(pCtx, pValue->v.val_vhash, &uValueSize)  );
	}

	// if this is a vhash with children, then recurse into it
	// otherwise provide it to the callback
	if (uValueSize > 0u)
	{
		// use our full name as the prefix during recursion
		// to accomplish that, we'll swap pData->pPrefix and pFullName
		// that way if an error occurs during recursion, everything will still be freed
		SG_string* pTemp = NULL;
		pTemp = pData->pPrefix;
		pData->pPrefix = pFullName;
		pFullName = pTemp;
		SG_ERR_CHECK(  SG_vhash__foreach(pCtx, pValue->v.val_vhash, provide_matching_values, pData)  );
		pTemp = pData->pPrefix;
		pData->pPrefix = pFullName;
		pFullName = pTemp;
	}
	else
	{
		// if we have a pattern that starts with a slash
		// then match it against the start of the full name
		// if the name doesn't match, skip this one
		if (pData->szPattern != NULL && pData->szPattern[0] == '/')
		{
			if (strncmp(SG_string__sz(pFullName), pData->szPattern, strlen(pData->szPattern)) != 0)
			{
				goto fail;
			}
		}

		// split our full name into a scope name and a local name
		SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pScopeName)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pSettingName)  );
		SG_ERR_CHECK(  SG_localsettings__split_full_name(pCtx, SG_string__sz(pFullName), NULL, pScopeName, pSettingName)  );

		// if we have a pattern that doesn't start with a slash
		// then match it against just the local name of the setting
		// if the name doesn't match, skip this one
		if (pData->szPattern != NULL && pData->szPattern[0] != '/')
		{
			if (strstr(SG_string__sz(pSettingName), pData->szPattern) == NULL)
			{
				goto fail;
			}
		}

		// send the data to the callback
		pData->pCallback(pCtx, SG_string__sz(pFullName), SG_string__sz(pScopeName), SG_string__sz(pSettingName), pValue, pData->pCallerData);
	}

fail:
	SG_STRING_NULLFREE(pCtx, pFullName);
	SG_STRING_NULLFREE(pCtx, pScopeName);
	SG_STRING_NULLFREE(pCtx, pSettingName);
}