void SG_curl__get_response_headers(SG_context * pCtx, SG_curl * pCurl, SG_string ** ppHeaders)
{
	_sg_curl* pMe = (_sg_curl*)pCurl;
	
	SG_ASSERT(pMe->pstrRawHeaders!=NULL);
	
	SG_ERR_CHECK_RETURN( SG_STRING__ALLOC__COPY(pCtx, ppHeaders, pMe->pstrRawHeaders) );
}
void sg_wc_liveview_item__alloc__clone_from_prescan(SG_context * pCtx,
													sg_wc_liveview_item ** ppLVI,
													SG_wc_tx * pWcTx,
													const sg_wc_prescan_row * pPrescanRow)
{
	sg_wc_liveview_item * pLVI = NULL;
	SG_bool bFoundIssue = SG_FALSE;

	SG_ERR_CHECK(  SG_alloc1(pCtx, pLVI)  );

	// caller needs to set the backptr if appropriate
	// if/when it adds this LVI to the LVD's vector.
	pLVI->pLiveViewDir = NULL;

	pLVI->uiAliasGid = pPrescanRow->uiAliasGid;

	SG_ERR_CHECK(  SG_STRING__ALLOC__COPY(pCtx,
										  &pLVI->pStringEntryname,
										  pPrescanRow->pStringEntryname)  );

	pLVI->pPrescanRow = pPrescanRow;	// borrow a reference rather than cloning the scanrow.

	// because a liveview_item must start as an
	// exact clone of a scanrow, there cannot be
	// any in-tx changes yet for it.
	pLVI->pPcRow_PC = NULL;

	pLVI->scan_flags_Live = pPrescanRow->scan_flags_Ref;
	pLVI->tneType = pPrescanRow->tneType;

	SG_ERR_CHECK(  sg_wc_db__issue__get_issue(pCtx, pWcTx->pDb,
											  pLVI->uiAliasGid,
											  &bFoundIssue,
											  &pLVI->statusFlags_x_xr_xu,
											  &pLVI->pvhIssue,
											  &pLVI->pvhSavedResolutions)  );

	*ppLVI = pLVI;
	return;

fail:
	SG_WC_LIVEVIEW_ITEM__NULLFREE(pCtx, pLVI);
}
/**
 * Return a pathname (live or temp) to a file that contains
 * the CURRENTLY QUEUED content that this item **SHOULD** have
 * at this point in the TX.
 *
 * That is, the caller could be in the middle of a TX and have
 * overwritten the file once or twice and then may now be
 * requesting the path to show a diff.  Or the file content may
 * be unchanged, but we have queued one or more moves/renames to
 * it or parent directories.
 *
 * As a good player INSIDE THE TX, we need to give them a path
 * to a CURRENT IN-TX COPY OF THE ***CONTENT*** (wherever it
 * may be).
 *
 * So the path we return may be to a temp file that was created
 * as a source for a QUEUED overwrite.  Or it may be a path to
 * the unmodified content in the WD -- WHERE IT WAS BEFORE THE
 * TX -- because until APPLY is called, the WD hasn't been
 * changed yet.
 *
 * Regardless of whether the result is a temp file or not, the
 * caller should be careful to not let the user modify the file
 * without participating in the TX.  That is, if we return the
 * actual non-temp working copy of a file and they use it in a
 * DIFF and the user's difftool is interactive and they alter
 * it and then we cancel the TX, what should the WD version of
 * the file contain?
 * 
 * See also:
 * __overwrite_file_from_file()
 * __overwrite_file_from_repo()
 * __add_special()
 * __undo_delete()
 *
 *
 * We return an indication of whether the file is a TEMP file
 * and shouldn't be written to.  It DOES NOT indicate that you
 * can delete it -- it indicates that you should not edit it because
 * *WE* will probably delete the file if the TX is rolled-back and so
 * the user would lose their edits.
 * 
 */
void sg_wc_liveview_item__get_proxy_file_path(SG_context * pCtx,
											  sg_wc_liveview_item * pLVI,
											  SG_wc_tx * pWcTx,
											  SG_pathname ** ppPath,
											  SG_bool * pbIsTmp)
{
	SG_string * pStringRepoPath = NULL;
	SG_pathname * pPathAbsolute = NULL;
	char * pszGid = NULL;
	const char * psz;
	SG_bool bIsTmp = SG_TRUE;

	if (pLVI->tneType != SG_TREENODEENTRY_TYPE_REGULAR_FILE)
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
							   (pCtx, "GetProxyFilePath: '%s' is not a file.",
								SG_string__sz(pLVI->pStringEntryname))  );

	if (pLVI->queuedOverwrites.pvhContent == NULL)
	{
		// No changes to the content yet in this TX.  Return the PRE-TX
		// pathname of this file.  (We may have QUEUED moves/renames on
		// the file or a parent directory, but they haven't been applied
		// yet.)

		SG_ASSERT(  pLVI->pPrescanRow  );
		SG_ASSERT(  pLVI->pPrescanRow->pStringEntryname  );
		SG_ASSERT(  pLVI->pPrescanRow->pPrescanDir_Ref  );
		SG_ASSERT(  pLVI->pPrescanRow->pPrescanDir_Ref->pStringRefRepoPath  );

		SG_ERR_CHECK(  SG_STRING__ALLOC__COPY(pCtx,
											  &pStringRepoPath,
											  pLVI->pPrescanRow->pPrescanDir_Ref->pStringRefRepoPath)  );
		SG_ERR_CHECK(  SG_repopath__append_entryname(pCtx,
													 pStringRepoPath,
													 SG_string__sz(pLVI->pPrescanRow->pStringEntryname),
													 SG_FALSE)  );
		SG_ERR_CHECK(  sg_wc_db__path__repopath_to_absolute(pCtx, pWcTx->pDb,
															pStringRepoPath,
															&pPathAbsolute)  );
		bIsTmp = SG_FALSE;	// path is to actual WC file
		goto done;
	}

	SG_ERR_CHECK_RETURN(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "file", &psz)  );
	if (psz)
	{
		// return path to existing TEMP file.  someone else owns the file.

		SG_ERR_CHECK(  SG_PATHNAME__ALLOC__SZ(pCtx, &pPathAbsolute, psz)  );
		bIsTmp = SG_TRUE;	// path is to a TEMP file (for which an overwrite-from-file has already been scheduled).
		goto done;
	}

	SG_ERR_CHECK_RETURN(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "hid", &psz)  );
	if (psz)
	{
		// synthesize a TEMP file for this.  caller owns the new temp file.

		SG_ERR_CHECK(  sg_wc_db__gid__get_gid_from_alias(pCtx, pWcTx->pDb, pLVI->uiAliasGid, &pszGid)  );
		SG_ERR_CHECK(  sg_wc_diff_utils__export_to_temp_file(pCtx, pWcTx, "ref", pszGid, psz,
															 SG_string__sz(pLVI->pStringEntryname),	// for suffix only
															 &pPathAbsolute)  );
		bIsTmp = SG_TRUE;	// path is to a TEMP file that we just created.
		goto done;
	}

	SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
						   (pCtx,
							"GetProxyFilePath: required field missing from vhash for: %s",
							SG_string__sz(pLVI->pStringEntryname))  );

done:
#if TRACE_WC_LIE
	SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
							   "GetProxyFilePath: '%s' ==> '%s' [bIsTmp %d]\n",
							   SG_string__sz(pLVI->pStringEntryname),
							   SG_pathname__sz(pPathAbsolute),
							   bIsTmp)  );
#endif
	*ppPath = pPathAbsolute;
	pPathAbsolute = NULL;
	*pbIsTmp = bIsTmp;

fail:
	SG_PATHNAME_NULLFREE(pCtx, pPathAbsolute);
	SG_STRING_NULLFREE(pCtx, pStringRepoPath);
	SG_NULLFREE(pCtx, pszGid);
}
/**
 * This is a special case of __get_current_content_hid() that
 * basically does that and then fetches the blob and returns
 * the actual target string contained within.
 *
 * Normally, we wouldn't need this specialization, but if we
 * have a QUEUED overwrite-symlink-target, THEN WE NEED TO LIE
 * and return the new/queued value rather than the actual
 * target value that the existing symlink has in the WD.
 *
 */
void sg_wc_liveview_item__get_current_symlink_target(SG_context * pCtx,
													 sg_wc_liveview_item * pLVI,
													 SG_wc_tx * pWcTx,
													 SG_string ** ppStringTarget)
{
	SG_byte*    pBuffer   = NULL;
	SG_uint64   uSize     = 0u;

	if (pLVI->tneType != SG_TREENODEENTRY_TYPE_SYMLINK)
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
							   (pCtx, "GetCurrentSymlinkTarget: '%s' is not a symlink.",
								SG_string__sz(pLVI->pStringEntryname))  );

	if (pLVI->queuedOverwrites.pvhContent)
	{
		// We have a QUEUED operation on this item that changed the
		// contents.  Get the 'current' value from the journal.

		const char * psz = NULL;

		SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "target", &psz)  );
		if (psz)
		{
			// last overwrite-type operation gave us a SYMLINK-TARGET.

#if TRACE_WC_LIE
			SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
									   "GetCurrentSymlinkTarget: using journal '%s' for: %s\n",
									   psz, SG_string__sz(pLVI->pStringEntryname))  );
#endif
			SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringTarget, psz)  );
			return;
		}

		SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "file", &psz)  );
		if (psz)
		{
			// last overwrite-type operation used a TEMP file.
			// this cannot/should not happen.
			SG_ERR_THROW2(  SG_ERR_INVALIDARG,
							(pCtx,
							 "GetCurrentSymlinkTarget: journal contains temp file '%s' for: %s",
							 psz, SG_string__sz(pLVI->pStringEntryname))  );
		}

		SG_ERR_CHECK(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "hid", &psz)  );
		if (psz)
		{
			// last overwrite-type operation used an HID.
			SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo, psz, &pBuffer, &uSize)  );
			SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
			SG_NULLFREE(pCtx, pBuffer);
			return;
		}
		
		SG_ERR_THROW2(  SG_ERR_NOTIMPLEMENTED,
						(pCtx,
						 "GetCurrentSymlinkTarget: required field missing from vhash for: %s",
						 SG_string__sz(pLVI->pStringEntryname))  );
	}

	// Otherwise, return the current pre-tx value.

	SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow) );

	if (SG_WC_PRESCAN_FLAGS__IS_CONTROLLED_SPARSE(pLVI->scan_flags_Live))
	{
		if (pLVI->pPcRow_PC)
		{
			SG_ASSERT_RELEASE_RETURN( (pLVI->pPcRow_PC->p_d_sparse) );
			SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo,
														   pLVI->pPcRow_PC->p_d_sparse->pszHid,
														   &pBuffer, &uSize)  );
			SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
			SG_NULLFREE(pCtx, pBuffer);
			return;
		}
		else if (pLVI->pPrescanRow->pPcRow_Ref)
		{
			SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse) );
			SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo,
														   pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse->pszHid,
														   &pBuffer, &uSize)  );
			SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
			SG_NULLFREE(pCtx, pBuffer);
			return;
		}
		else
		{
			// With the addition of {sparse_hid,sparse_attrbits} to tbl_PC,
			// we should not get here.
			SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
								   (pCtx, "GetCurrentSymlinkTarget: unhandled case when sparse for '%s'.",
									SG_string__sz(pLVI->pStringEntryname))  );
		}
	}
	else if (pLVI->pPrescanRow->pRD)
	{
		const SG_string * pStringTargetRef;
		SG_ERR_CHECK(  sg_wc_readdir__row__get_content_symlink_target(pCtx, pWcTx, pLVI->pPrescanRow->pRD,
																	  &pStringTargetRef)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__COPY(pCtx, ppStringTarget, pStringTargetRef)  );
		return;
	}
	else if (pLVI->pPrescanRow->pTneRow)
	{
		SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo,
													   pLVI->pPrescanRow->pTneRow->p_d->pszHid,
													   &pBuffer, &uSize)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, ppStringTarget, pBuffer, (SG_uint32)uSize)  );
		SG_NULLFREE(pCtx, pBuffer);
		return;
	}
	else
	{
		// perhaps an ADD-SPECIAL + DELETE
		// or an ADDED+LOST in an UPDATE ?
		SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
							   (pCtx, "GetCurrentSymlinkTarget: unhandled case for '%s'.",
								SG_string__sz(pLVI->pStringEntryname))  );
	}


fail:
	SG_NULLFREE(pCtx, pBuffer);
}
void sg_wc_tx__rp__undo_delete__lvi_lvi_sz(SG_context * pCtx,
										   SG_wc_tx * pWcTx,
										   sg_wc_liveview_item * pLVI_Src,
										   sg_wc_liveview_item * pLVI_DestDir,
										   const char * pszEntryname_Dest,
										   const SG_wc_undo_delete_args * pArgs,
										   SG_wc_status_flags xu_mask)
{
	SG_string * pStringRepoPath_Src_Computed = NULL;
	SG_string * pStringRepoPath_DestDir_Computed = NULL;
	SG_string * pStringRepoPath_Dest_Computed = NULL;
	sg_wc_liveview_item * pLVI_Dest;		// we do not own this
	sg_wc_liveview_dir * pLVD_DestDir;		// we do not own this
	SG_bool bKnown_Dest;

	SG_NULLARGCHECK_RETURN( pWcTx );
	SG_NULLARGCHECK_RETURN( pLVI_Src );
	SG_NULLARGCHECK_RETURN( pLVI_DestDir );
	SG_NONEMPTYCHECK_RETURN( pszEntryname_Dest );
	// pArgs is optional

	SG_ERR_CHECK(  sg_wc_tx__liveview__compute_live_repo_path(pCtx, pWcTx, pLVI_Src,
															  &pStringRepoPath_Src_Computed)  );
	SG_ERR_CHECK(  sg_wc_tx__liveview__compute_live_repo_path(pCtx, pWcTx, pLVI_DestDir,
															  &pStringRepoPath_DestDir_Computed)  );

	if (SG_WC_PRESCAN_FLAGS__IS_CONTROLLED_DELETED(pLVI_Src->scan_flags_Live) == SG_FALSE)
		SG_ERR_THROW2(  SG_ERR_WC__ITEM_ALREADY_EXISTS,
						(pCtx, "Source '%s'",
						 SG_string__sz(pStringRepoPath_Src_Computed))  );

	if (SG_WC_PRESCAN_FLAGS__IS_CONTROLLED_DELETED(pLVI_DestDir->scan_flags_Live))
		SG_ERR_THROW2(  SG_ERR_NOT_FOUND,
						(pCtx, "The destination directory '%s' has been REMOVED.",
						 SG_string__sz(pStringRepoPath_DestDir_Computed))  );
	if (SG_WC_PRESCAN_FLAGS__IS_CONTROLLED_LOST(pLVI_DestDir->scan_flags_Live))
		SG_ERR_THROW2(  SG_ERR_NOT_FOUND,
						(pCtx, "The destination directory '%s' is currently LOST.",
						 SG_string__sz(pStringRepoPath_DestDir_Computed))  );
	
	SG_ERR_CHECK(  SG_STRING__ALLOC__COPY(pCtx,
										  &pStringRepoPath_Dest_Computed,
										  pStringRepoPath_DestDir_Computed)  );
	SG_ERR_CHECK(  SG_repopath__append_entryname(pCtx,
												 pStringRepoPath_Dest_Computed,
												 pszEntryname_Dest,
												 SG_FALSE)  );
	SG_ERR_CHECK(  sg_wc_tx__liveview__fetch_item(pCtx, pWcTx,
												  pStringRepoPath_Dest_Computed,
												  &bKnown_Dest, &pLVI_Dest)  );
	if (bKnown_Dest)
		SG_ERR_THROW2(  SG_ERR_WC__ITEM_ALREADY_EXISTS,
						(pCtx, "The destination '%s' already exists.",
						 SG_string__sz(pStringRepoPath_Dest_Computed))  );

	// I'm not going to worry about path-cycle problems
	// because we already know that the source does not
	// exist and the destination directory does, so it
	// isn't possible to have a cycle.

	SG_ERR_CHECK(  sg_wc_tx__liveview__fetch_dir(pCtx, pWcTx, pLVI_DestDir, &pLVD_DestDir)  );
	SG_ERR_CHECK(  sg_wc_liveview_dir__can_add_new_entryname(pCtx, pWcTx->pDb,
															 pLVD_DestDir,
															 NULL,
															 NULL,
															 pszEntryname_Dest,
															 SG_TREENODEENTRY_TYPE__INVALID,
															 SG_FALSE)  );

	SG_ERR_CHECK(  sg_wc_tx__queue__undo_delete(pCtx, pWcTx,
												pStringRepoPath_Dest_Computed,
												pLVI_Src,
												pLVI_DestDir,
												pszEntryname_Dest,
												pArgs,
												xu_mask)  );

fail:
	SG_STRING_NULLFREE(pCtx, pStringRepoPath_Src_Computed);
	SG_STRING_NULLFREE(pCtx, pStringRepoPath_DestDir_Computed);
	SG_STRING_NULLFREE(pCtx, pStringRepoPath_Dest_Computed);
}
Example #6
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);
}
Example #7
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);
}