void SG_curl__perform(SG_context* pCtx, SG_curl* pCurl)
{
	CURLcode rc = CURLE_OK;
	_sg_curl* pMe = (_sg_curl*)pCurl;
	
	SG_NULLARGCHECK_RETURN(pCurl);
	
	rc = curl_easy_perform(pMe->pCurl);
	
	// Check for errors in the request and response callbacks.
	SG_ERR_CHECK_RETURN_CURRENT;

	// No callback errors. Make sure curl result code is also kosher.
	if (rc)
	{
		switch (rc)
		{
			case CURLE_SEND_ERROR:
			case CURLE_RECV_ERROR:
				SG_ERR_THROW2_RETURN(SG_ERR_LIBCURL(rc), (pCtx, "%s", "please try again."));
				break;
			
#if defined(WINDOWS)
			case CURLE_SSL_CACERT:
				SG_ERR_THROW2_RETURN(SG_ERR_LIBCURL(rc), (pCtx, "Verify that the server/files setting is correct, or use the command 'vv config set network/verify_ssl_certs false' to disable this warning."));
				break;
#endif

			default:
				SG_ERR_THROW_RETURN(SG_ERR_LIBCURL(rc));
		}
	}
}
void sg_sync_client__bind_vtable(SG_context* pCtx, SG_sync_client * pClient)
{
	SG_bool bRemote = SG_FALSE;

	SG_NULLARGCHECK_RETURN(pClient);

	if (pClient->p_vtable) // can only be bound once
		SG_ERR_THROW2_RETURN(SG_ERR_INVALIDARG, (pCtx, "vtable already bound"));

	// Select the vtable based on pClient's remote repo specification or the presence of a local repo handle.
	if (pClient->psz_remote_repo_spec)
		SG_ERR_CHECK_RETURN(  SG_sync_client__spec_is_remote(pCtx, pClient->psz_remote_repo_spec, &bRemote)  );
	else if (!pClient->pRepoOther)
		SG_ERR_THROW2_RETURN(SG_ERR_INVALIDARG, (pCtx, "a repo spec or a local repo handle must be set"));
		
	if (bRemote)
		pClient->p_vtable = &s_client_vtable__http;
	else
	{
		pClient->p_vtable = &s_client_vtable__c;
		/* It would be better if this were done inside the C vtable, where pClient->pRepoOther is 
		 * actually used, but there's not a sane way to do that now; it would require a significant 
		 * re-org. */
		if (!pClient->pRepoOther)
		{
			SG_ERR_CHECK_RETURN(  SG_REPO__OPEN_REPO_INSTANCE(pCtx, pClient->psz_remote_repo_spec, &pClient->pRepoOther)  );
			pClient->bRepoOtherIsMine = SG_TRUE;
		}
	}
}
void SG_jscore__new_context(SG_context * pCtx, JSContext ** pp_cx, JSObject ** pp_glob,
	const SG_vhash * pServerConfig)
{
	JSContext * cx = NULL;
	JSObject * glob = NULL;

	SG_ASSERT(pCtx!=NULL);
	SG_NULLARGCHECK_RETURN(pp_cx);

	if(gpJSCoreGlobalState==NULL)
		SG_ERR_THROW2_RETURN(SG_ERR_UNINITIALIZED, (pCtx, "jscore has not been initialized"));

	if (gpJSCoreGlobalState->cb)
		JS_SetContextCallback(gpJSCoreGlobalState->rt, gpJSCoreGlobalState->cb);

	cx = JS_NewContext(gpJSCoreGlobalState->rt, 8192);
	if(cx==NULL)
		SG_ERR_THROW2_RETURN(SG_ERR_MALLOCFAILED, (pCtx, "Failed to allocate new JS context"));

	(void)JS_SetContextThread(cx);
	JS_BeginRequest(cx);

	JS_SetOptions(cx, JSOPTION_VAROBJFIX);
	JS_SetVersion(cx, JSVERSION_LATEST);
	JS_SetContextPrivate(cx, pCtx);

	glob = JS_NewCompartmentAndGlobalObject(cx, &global_class, NULL);
	if(glob==NULL)
		SG_ERR_THROW2(SG_ERR_JS, (pCtx, "Failed to create JavaScript global object for new JSContext."));
	if(!JS_InitStandardClasses(cx, glob))
		SG_ERR_THROW2(SG_ERR_JS, (pCtx, "JS_InitStandardClasses() failed."));
	if (gpJSCoreGlobalState->shell_functions)
		if (!JS_DefineFunctions(cx, glob, gpJSCoreGlobalState->shell_functions))
			SG_ERR_THROW2(SG_ERR_JS, (pCtx, "Failed to install shell functions"));

    SG_jsglue__set_sg_context(pCtx, cx);

	SG_ERR_CHECK(  SG_jsglue__install_scripting_api(pCtx, cx, glob)  );
	SG_ERR_CHECK(  SG_zing_jsglue__install_scripting_api(pCtx, cx, glob)  );

	if (! gpJSCoreGlobalState->bSkipModules)
	{
		_sg_jscore__install_modules(pCtx, cx, glob, pServerConfig);
		SG_ERR_CHECK_CURRENT_DISREGARD(SG_ERR_NOTAFILE);
	}

	*pp_cx = cx;
	*pp_glob = glob;

	return;
fail:
	if (cx)
	{
		JS_EndRequest(cx);
		JS_DestroyContext(cx);
	}
}
void SG_curl__set_headers_from_varray(SG_context * pCtx, SG_curl * pCurl, SG_varray * pvaHeaders, struct curl_slist ** ppHeaderList)
{
	CURLcode rc = CURLE_OK;
    _sg_curl* p = (_sg_curl*)pCurl;
	struct curl_slist* pHeaderList = NULL;
	SG_uint32 count = 0;
	SG_uint32 i = 0;
	
	SG_NULLARGCHECK_RETURN(pCurl);
	SG_NULLARGCHECK_RETURN(pvaHeaders);
	
	SG_ERR_CHECK( SG_varray__count(pCtx, pvaHeaders, &count) );
	for (i = 0; i < count; i++)
	{
		const char * psz = NULL;
		SG_ERR_CHECK_RETURN( SG_varray__get__sz(pCtx, pvaHeaders, i, &psz) );
		pHeaderList = curl_slist_append(pHeaderList, psz);
		if (!pHeaderList)
			SG_ERR_THROW2_RETURN(SG_ERR_UNSPECIFIED, (pCtx, "Failed to add HTTP header."));
		
	}
	
	rc = curl_easy_setopt(p->pCurl, CURLOPT_HTTPHEADER, pHeaderList);
	if (rc)
		SG_ERR_THROW2(SG_ERR_LIBCURL(rc), (pCtx, "Problem setting HTTP headers" ));

	SG_RETURN_AND_NULL(pHeaderList, ppHeaderList);
	
fail:
	if (pHeaderList)
		SG_CURL_HEADERS_NULLFREE(pCtx, pHeaderList);

}
/**
 * This is a special case of __get_original_content_hid() that
 * basically doest that and then fetches the blob and returns
 * the actual symlink target.
 *
 * This is a convenience for STATUS that can report 'target'
 * fields rather than (or in addition to) 'hid' for symlinks.
 *
 */
void sg_wc_liveview_item__get_original_symlink_target(SG_context * pCtx,
													  sg_wc_liveview_item * pLVI,
													  SG_wc_tx * pWcTx,
													  SG_string ** ppStringTarget)
{
	const char * pszHidContent;
	SG_string * pStringTarget = NULL;
	SG_byte*    pBuffer   = NULL;
	SG_uint64   uSize     = 0u;

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

	SG_ERR_CHECK(  sg_wc_liveview_item__get_original_content_hid(pCtx, pLVI, pWcTx,
																 SG_FALSE,
																 &pszHidContent)  );
	SG_ERR_CHECK(  SG_repo__fetch_blob_into_memory(pCtx, pWcTx->pDb->pRepo,
												   pszHidContent,
												   &pBuffer, &uSize)  );
	SG_ERR_CHECK(  SG_STRING__ALLOC__BUF_LEN(pCtx, &pStringTarget, pBuffer, (SG_uint32)uSize)  );

	*ppStringTarget = pStringTarget;
	pStringTarget = NULL;

fail:
	SG_STRING_NULLFREE(pCtx, pStringTarget);
	SG_NULLFREE(pCtx, pBuffer);
}
void SG_dagquery__new_since(SG_context * pCtx,
							SG_repo* pRepo,
							SG_uint64 iDagNum,
							const char* pszOldNodeHid,
							const char* pszNewNodeHid,
							SG_rbtree** pprbNewNodeHids)
{
	SG_dagquery_relationship rel;
	new_since_context cbCtx;

    cbCtx.dagnum = iDagNum;
	cbCtx.prbNewNodeHids = NULL;
	cbCtx.pszNewNodeHid = pszNewNodeHid;
	cbCtx.pszOldNodeHid = pszOldNodeHid;

	SG_ERR_CHECK(  SG_dagquery__how_are_dagnodes_related(pCtx, pRepo, iDagNum, pszNewNodeHid, pszOldNodeHid, SG_FALSE, SG_FALSE, &rel)  );

	if (rel != SG_DAGQUERY_RELATIONSHIP__DESCENDANT)
		SG_ERR_THROW2_RETURN(SG_ERR_UNSPECIFIED, (pCtx, "pszNewNodeHid must be a descendant of pszOldNodeHid"));

	SG_ERR_CHECK(  SG_RBTREE__ALLOC(pCtx, &cbCtx.prbNewNodeHids)  );

	SG_ERR_CHECK(  SG_dagwalker__walk_dag_single(pCtx, pRepo, iDagNum, pszNewNodeHid, _dagquery__new_since__callback, (void*)&cbCtx)  );

	SG_RETURN_AND_NULL(cbCtx.prbNewNodeHids, pprbNewNodeHids);

	/* Fall through to common cleanup */
fail:
	SG_RBTREE_NULLFREE(pCtx, cbCtx.prbNewNodeHids);
}
static void _complain_if_unnormalized(SG_context * pCtx, const char * pszInput)
{
	const char * psz;

	SG_ASSERT(  (pszInput[0] == '/')  );

	for (psz=pszInput; *psz; psz++)
	{
		if (psz[0]=='/')
		{
			if ((psz[1]=='.') && ((psz[2]=='/') || (psz[2]==0)))
				goto my_throw;		// disallow "/./" or a trailing "/."
		
			if ((psz[1]=='.') && (psz[2]=='.') && ((psz[3]=='/') || (psz[3]==0)))
				goto my_throw;		// disallow "/../" or a trailing "/.."

			if ((psz[1]=='/'))
				goto my_throw;		// disallow "//"
		}

		if (psz[0]=='\\')
			goto my_throw;			// disallow backslashes anywhere in repo-paths
	}

	return;

my_throw:
	SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
						   (pCtx, "The input '%s' is not a valid repo-path (unnormalized).", pszInput)  );
}
static void _check_if_relative_paths_allowed(SG_context* pCtx, const sg_wc_db * pDb)
{
	if (SG_FALSE == pDb->bWorkingDirPathFromCwd)
	{
		SG_ERR_THROW2_RETURN( SG_ERR_INVALIDARG, (pCtx, 
			"Relative paths are allowed only in working copy transactions initiated in the current working directory.") );
	}
}
Exemplo n.º 9
0
void SG_workingdir__construct_absolute_path_from_repo_path(SG_context * pCtx,
														   const SG_pathname * pPathWorkingDirectoryTop,
														   const char * pszRepoPath,
														   SG_pathname ** ppPathAbsolute)
{
	SG_pathname * pPath = NULL;

#if defined(DEBUG)
	// TODO we should verify "<wd-top>/.sgdrawer" exists to make sure
	// TODO that we have the actual wd-root.  especially when using vv
	// TODO with relative paths while cd'd somewhere deep inside the tree.
#endif

	if (pszRepoPath[0] != '@')
	{
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALID_REPO_PATH,
							   (pCtx, "Must begin with '@/': %s", pszRepoPath)  );
	}
	else
	{
		if (pszRepoPath[1] == 0)		// quietly allow "@" as substitute for "@/"
		{
			SG_ERR_CHECK(  SG_PATHNAME__ALLOC__COPY(pCtx, &pPath, pPathWorkingDirectoryTop)  );
		}
		else if (pszRepoPath[1] != '/')
		{
			SG_ERR_THROW2_RETURN(  SG_ERR_INVALID_REPO_PATH,
								   (pCtx, "Must begin with '@/': %s", pszRepoPath)  );
		}
		else
		{
			if (pszRepoPath[2])
				SG_ERR_CHECK(  SG_PATHNAME__ALLOC__PATHNAME_SZ(pCtx, &pPath, pPathWorkingDirectoryTop, &pszRepoPath[2])  );
			else
				SG_ERR_CHECK(  SG_PATHNAME__ALLOC__COPY(pCtx, &pPath, pPathWorkingDirectoryTop)  );
		}
	}

	*ppPathAbsolute = pPath;
	return;

fail:
	SG_PATHNAME_NULLFREE(pCtx, pPath);
}
static void _start_thread(SG_context * pCtx, LPTHREAD_START_ROUTINE pFunc, void* pParam, HANDLE* ppHandle)
{
	HANDLE	hThread;

	SG_UNUSED(pCtx);

	hThread = CreateThread(NULL, 0, pFunc, pParam, 0, NULL);
	

	if (hThread != NULL)
		*ppHandle = hThread;
	else
	{
		SG_ERR_THROW2_RETURN(SG_ERR_GETLASTERROR(GetLastError()), 
			(pCtx, "%s", "CreateThread failed"));
	}
}
static void _start_thread(SG_context * pCtx, void* pFunc, void* pParam, pthread_t* pThreadId)
// pCtx: input parameter only (used for logging)
{
	pthread_t	thread_id;
//	pthread_attr_t	attr;
	int		retval;

//	(void) pthread_attr_init(&attr);
//	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	retval = pthread_create(&thread_id, NULL, pFunc, pParam);
	if (retval)
		SG_ERR_THROW2_RETURN(SG_ERR_ERRNO(retval), 
			(pCtx, "%s", "pthread_create failed"));

	*pThreadId = thread_id;
}
void SG_password__set(
	SG_context* pCtx,
	const char *szRepoSpec,
	SG_string *pstrUserName,
	SG_string *pstrPassword)
{
	SG_string* pstrTarget = NULL;
	LPWSTR pwszTarget = NULL;
	LPWSTR pwszPassword = NULL;
	SG_uint32 lenPassword = 0;
	LPWSTR pwszUserName = NULL;
	CREDENTIAL cred;

	SG_NULLARGCHECK_RETURN(szRepoSpec);
	SG_NULLARGCHECK_RETURN(pstrUserName);
	SG_NULLARGCHECK_RETURN(pstrPassword);
	if (!SG_string__length_in_bytes(pstrUserName))
		SG_ERR_THROW2_RETURN(SG_ERR_INVALIDARG, (pCtx, "%s", "pstrUserName is empty"));

	SG_ERR_CHECK(  _get_key(pCtx, szRepoSpec, SG_string__sz(pstrUserName), &pstrTarget)  );
	SG_ERR_CHECK(  SG_utf8__extern_to_os_buffer__wchar(pCtx, SG_string__sz(pstrTarget), &pwszTarget, NULL)  );
	SG_ERR_CHECK(  SG_utf8__extern_to_os_buffer__wchar(pCtx, SG_string__sz(pstrPassword), &pwszPassword, &lenPassword)  );
	SG_ERR_CHECK(  SG_utf8__extern_to_os_buffer__wchar(pCtx, SG_string__sz(pstrUserName), &pwszUserName, NULL)  );

	SG_zero(cred);
	cred.Type = CRED_TYPE_GENERIC;
	cred.TargetName = pwszTarget;
	cred.CredentialBlob = (LPBYTE)pwszPassword;
	cred.CredentialBlobSize = lenPassword*sizeof(wchar_t);
	cred.Persist = CRED_PERSIST_LOCAL_MACHINE; // unsupported on Windows Vista Home Basic, Windows Vista Home Premium, Windows Vista Starter, and Windows XP Home Edition
	cred.UserName = pwszUserName;

	if ( !CredWriteW(&cred, 0) )
		SG_ERR_THROW2( SG_ERR_GETLASTERROR(GetLastError()), (pCtx, "%s", "unable to save credentials") );

	/* fall through */
fail:
	SG_STRING_NULLFREE(pCtx, pstrTarget);
	SG_NULLFREE(pCtx, pwszTarget);
	SG_NULLFREE(pCtx, pwszPassword);
	SG_NULLFREE(pCtx, pwszUserName);
}
Exemplo n.º 13
0
static void _read_file_chunk(SG_context* pCtx, SG_curl* pCurl, char* buffer, SG_uint32 bufLen, void* pVoidState, SG_uint32* pLenHandled)
{
	struct _sg_curl__file_read_state* pReadState = (struct _sg_curl__file_read_state*)pVoidState;

	SG_UNUSED(pCurl);

	if (pReadState->finished)
	{
		SG_ERR_THROW2_RETURN(SG_ERR_UNSPECIFIED, (pCtx, "An unknown error occurred interfacing with libcurl. Please try again."));
	}
	else if (pReadState->pos == pReadState->len)
	{
		pReadState->finished = SG_TRUE;
		*pLenHandled = 0;
	}
	else
	{
		SG_ERR_CHECK_RETURN(  SG_file__read(pCtx, pReadState->pFile, bufLen, (SG_byte*)buffer, pLenHandled)  );
		pReadState->pos += *pLenHandled;
	}
}
Exemplo n.º 14
0
void sg_client__bind_vtable(SG_context* pCtx, SG_client * pClient)
{
	char* psz_protocol = NULL;

	SG_NULLARGCHECK_RETURN(pClient);
	SG_NULLARGCHECK_RETURN(pClient->psz_remote_repo_spec);

	if (pClient->p_vtable) // can only be bound once
		SG_ERR_THROW2_RETURN(SG_ERR_INVALIDARG, (pCtx, "vtable already bound"));

	// Select the vtable based on pClient's destination repo specification.
	if (strlen(pClient->psz_remote_repo_spec) > 6)
	{
		SG_ERR_CHECK(  SG_ascii__substring(pCtx, pClient->psz_remote_repo_spec, 0, 7, &psz_protocol)  );
		if (SG_stricmp("http://", (const char*)psz_protocol) == 0)
			pClient->p_vtable = &s_client_vtable__http;
	}
	if (!pClient->p_vtable)
		pClient->p_vtable = &s_client_vtable__c;

	// fall through
 fail:
 	SG_NULLFREE(pCtx, psz_protocol);
}
void SG_vv2__init_new_repo(SG_context * pCtx,
						   const char * pszRepoName,
						   const char * pszFolder,
						   const char * pszStorage,
						   const char * pszHashMethod,
						   SG_bool bNoWD,
						   const char * psz_shared_users,
						   SG_bool bFromUserMaster,
						   char ** ppszGidRepoId,
						   char ** ppszHidCSetFirst)
{
	char * pszGidRepoId = NULL;
	char * pszHidCSetFirst = NULL;

	if (bNoWD && pszFolder && *pszFolder)
	{
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
							   (pCtx, "init_new_repo: option 'no-wc' given with a folder argument.")  );
	}

	if (bFromUserMaster && psz_shared_users)
	{
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
			(pCtx, "init_new_repo: options 'shared-users' and 'from-user-master' are incompatible.")  );
	}
	// create the new repo.

	SG_ERR_CHECK(  _vv_verbs__init_new_repo__do_init(pCtx, pszRepoName, pszStorage, pszHashMethod, 
													 psz_shared_users, bFromUserMaster,
													 &pszGidRepoId, &pszHidCSetFirst)  );

	if (!bNoWD)
	{
		// try to create a new WD for the new repo.  the real goal here
		// is to create the directory (if necessary) and then the drawer
		// and control files.  if the directory already exists, we do not
		// auto-add the contents.

		SG_ERR_CHECK(  SG_wc__initialize(pCtx,
										 pszRepoName, pszFolder,
										 pszHidCSetFirst, SG_VC_BRANCHES__DEFAULT)  );
	}
	
	if (ppszGidRepoId)
		*ppszGidRepoId = pszGidRepoId;
	else
		SG_NULLFREE(pCtx, pszGidRepoId);

	if (ppszHidCSetFirst)
		*ppszHidCSetFirst = pszHidCSetFirst;
	else
		SG_NULLFREE(pCtx, pszHidCSetFirst);
	return;

fail:
	if (pszGidRepoId)
	{
		// If we successfully created the repo, but had an error
		// trying to populate the WD, delete the repo.  WE DO NOT
		// delete any mess we created in/around the WD because we
		// don't know how much of it the checkout created and how
		// much already existed.

		SG_ERR_IGNORE(  _vv_verbs__init_new_repo__delete_new_repo(pCtx, pszRepoName)  );
	}

	SG_NULLFREE(pCtx, pszGidRepoId);
	SG_NULLFREE(pCtx, pszHidCSetFirst);
}
/**
 * Import a raw string (probably as the user typed it) and
 * transform it into a repo-path.  And scrub it as appropriate.
 *
 * This routine is inside WC and takes a "pDb" because we need
 * to be able to interpret a null or relative path and convert
 * them into a wd-root-based current/live repo-path.
 *
 * But we also need to be able to call it for arbitrary-cset-based
 * repo-paths that when we don't have a WD.
 *
 * We return both the observed/computed repo-path and the
 * observed/computed repo-path-domain.  The returned repo-path
 * will contain the repo-path-domain so that they are always
 * in normal form.
 *
 * It is up to the caller to decide whether the domain is
 * valid for their purposes and/or throw.
 *
 */
void sg_wc_db__path__anything_to_repopath(SG_context * pCtx,
										  const sg_wc_db * pDb,
										  const char * pszInput,
										  sg_wc_db__path__import_flags flags,
										  SG_string ** ppStringRepoPath,
										  char * pcDomain)
{
	SG_pathname * pPathTemp = NULL;
	SG_string * pStringTemp = NULL;
	SG_bool bValid;

	if (!pszInput || !*pszInput)	// when null, assume either CWD or ROOT.
	{
		switch (flags)
		{
		default:
		case SG_WC_DB__PATH__IMPORT_FLAGS__TREAT_NULL_AS_ERROR:
			SG_ERR_THROW_RETURN(  SG_ERR_INVALIDARG  );

		case SG_WC_DB__PATH__IMPORT_FLAGS__TREAT_NULL_AS_ROOT:
			SG_ERR_CHECK(  SG_PATHNAME__ALLOC__COPY(pCtx, &pPathTemp, pDb->pPathWorkingDirectoryTop)  );
			SG_ERR_CHECK(  sg_wc_db__path__absolute_to_repopath(pCtx, pDb, pPathTemp, ppStringRepoPath)  );
			SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
			*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
			return;

		case SG_WC_DB__PATH__IMPORT_FLAGS__TREAT_NULL_AS_CWD:
			SG_ERR_CHECK(  SG_PATHNAME__ALLOC(pCtx, &pPathTemp)  );
			SG_ERR_CHECK(  SG_pathname__set__from_cwd(pCtx, pPathTemp)  );
			SG_ERR_CHECK(  sg_wc_db__path__absolute_to_repopath(pCtx, pDb, pPathTemp, ppStringRepoPath)  );
			SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
			*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
			return;
		}
	}

	if (pszInput[0] != '@')			// a non-repo pathname
	{
		SG_bool bPathIsRelative = SG_FALSE;
		SG_ERR_CHECK(  SG_pathname__is_relative(pCtx, pszInput, &bPathIsRelative)  );
		if (bPathIsRelative)
			SG_ERR_CHECK(  _check_if_relative_paths_allowed(pCtx, pDb)  );

		SG_ERR_CHECK(  SG_PATHNAME__ALLOC__SZ(pCtx, &pPathTemp, pszInput)  );
		SG_ERR_CHECK(  sg_wc_db__path__absolute_to_repopath(pCtx, pDb, pPathTemp, ppStringRepoPath)  );
		SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
		return;
	}

	// if they gave us something that started with a "@" we
	// either return it as is or throw if we can't parse it.
	// this ensures that any error messages don't leave the
	// domain out of the repo-path.

	switch (pszInput[1])
	{
	case 0:
		SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
							   (pCtx, "The input '%s' is not a valid repo-path input.", pszInput)  );

	case SG_WC__REPO_PATH_DOMAIN__G:		// domain "@g..." is a GID and not a path.
		// A '@g' domain input should look like
		// "@gae9ce8e7eaf04b58aef9470b439329f32249aae2358511e1b77b002500da2b78"
		// That is, the 'g' domain specifier is shared with the 'g' in the
		// full GID.  For now a full GID is required; we don't try to do the
		// unique prefix trick like we do for CSET HIDs in a REV-SPEC.  We
		// may think about adding that later.

		SG_ERR_CHECK(  SG_gid__verify_format(pCtx, &pszInput[1], &bValid)  );
		if (!bValid)
			SG_ERR_THROW2(  SG_ERR_INVALIDARG,
							(pCtx, "The input '%s' is not a valid gid-domain input.", pszInput)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__G;
		return;

	case SG_WC__REPO_PATH_DOMAIN__T:		// domain "@t..." is a temporary GID and not a path.
		// A '@t' domain input should look like
		// "@tae9ce8e7eaf04b58aef9470b439329f32249aae2358511e1b77b002500da2b78"
		// That is, the 't' domain specifier replaces the normal 'g' in the
		// full GID.  For now a full GID is required; we don't try to do the
		// unique prefix trick like we do for CSET HIDs in a REV-SPEC.  We
		// may think about adding that later.  We use a 't' when reporting
		// STATUS rather than a 'g' as a reminder that the ID is only valid
		// until the end of the transaction.
		//
		// make a g-version of the string so we can validate it.
		// but still return the t-version.

		SG_ERR_CHECK(  SG_STRING__ALLOC(pCtx, &pStringTemp)  );
		SG_ERR_CHECK(  SG_string__sprintf(pCtx, pStringTemp, "%c%s",
										  SG_WC__REPO_PATH_DOMAIN__G,
										  &pszInput[2])  );
		SG_ERR_CHECK(  SG_gid__verify_format(pCtx, SG_string__sz(pStringTemp), &bValid)  );
		SG_STRING_NULLFREE(pCtx, pStringTemp);
		if (!bValid)
			SG_ERR_THROW2(  SG_ERR_INVALIDARG,
							(pCtx, "The input '%s' is not a valid temp-gid-domain input.", pszInput)  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__T;
		return;

	case SG_WC__REPO_PATH_DOMAIN__LIVE:		// domain "@/foo..." is a normal current/live repo-path
		SG_ERR_CHECK(  _complain_if_unnormalized(pCtx, &pszInput[1])  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = SG_WC__REPO_PATH_DOMAIN__LIVE;
		return;

	default:		// we allow any other SINGLE character to be a domain.
		if (pszInput[2] != '/')
			SG_ERR_THROW2_RETURN(  SG_ERR_INVALIDARG,
								   (pCtx, "The input '%s' is not a valid repo-path input.", pszInput)  );

		// we have "@x/foo...." for some character x.
		SG_ERR_CHECK(  _complain_if_unnormalized(pCtx, &pszInput[2])  );
		SG_ERR_CHECK(  SG_STRING__ALLOC__SZ(pCtx, ppStringRepoPath, pszInput)  );
		*pcDomain = pszInput[1];
		return;
	}

fail:
	SG_PATHNAME_NULLFREE(pCtx, pPathTemp);
	SG_STRING_NULLFREE(pCtx, pStringTemp);
}
/**
 * Get the current attrbits observed on the item *UNIONED*
 * with the baseline, if present.
 *
 * If the item is LOST/DELETED (not present in the working
 * directory), fall back to the baseline value.
 *
 */
void sg_wc_liveview_item__get_current_attrbits(SG_context * pCtx,
											   sg_wc_liveview_item * pLVI,
											   SG_wc_tx * pWcTx,
											   SG_uint64 * pAttrbits)
{
	if (pLVI->queuedOverwrites.pvhAttrbits)
	{
		// We have a QUEUED operation on this item that set the attrbits.
		// get the value from the journal.

		SG_ERR_CHECK_RETURN(  SG_vhash__get__int64(pCtx, pLVI->queuedOverwrites.pvhAttrbits,
												   "attrbits",
												   (SG_int64 *)pAttrbits)  );
#if TRACE_WC_LIE
		{
			SG_int_to_string_buffer bufui64;
			SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
									   "GetCurrentAttrbits: using journal: %s\n",
									   SG_uint64_to_sz__hex((*pAttrbits), bufui64))  );
		}
#endif
		return;
	}

#if 1 && TRACE_WC_ATTRBITS
	{
	sg_wc_db__pc_row__flags_net flags_net = SG_WC_DB__PC_ROW__FLAGS_NET__ZERO;
	SG_ERR_IGNORE(  sg_wc_liveview_item__get_flags_net(pCtx, pLVI, &flags_net)  );
	SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
							   "GetCurrentAttrbits: [flagsLive %d][flagsNet 0x%02x][pPcRow_PC %04x][pPcRow_Ref %04x][pRD %04x][pTneRow %04x] %s\n",
							   ((SG_uint32)pLVI->scan_flags_Live),
							   ((SG_uint32)flags_net),
							   (SG_uint32)((pLVI->pPcRow_PC) ? (pLVI->pPcRow_PC->ref_attrbits) : 0xffff),
							   (SG_uint32)((pLVI->pPrescanRow->pPcRow_Ref) ? (pLVI->pPrescanRow->pPcRow_Ref->ref_attrbits) : 0xffff),
							   (SG_uint32)((pLVI->pPrescanRow->pRD && pLVI->pPrescanRow->pRD->pAttrbits) ? (*pLVI->pPrescanRow->pRD->pAttrbits) : 0xffff),
							   (SG_uint32)((pLVI->pPrescanRow->pTneRow && pLVI->pPrescanRow->pTneRow->p_d) ? (pLVI->pPrescanRow->pTneRow->p_d->attrbits) : 0xffff),
							   SG_string__sz(pLVI->pStringEntryname))  );
	}
#endif

	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) );
			*pAttrbits = pLVI->pPcRow_PC->p_d_sparse->attrbits;
		}
		else if (pLVI->pPrescanRow->pPcRow_Ref)
		{
			SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse) );
			*pAttrbits = pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse->attrbits;
		}
		else
		{
			SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
								   (pCtx, "GetCurrentAttrbits: unhandled case when sparse for '%s'.",
									SG_string__sz(pLVI->pStringEntryname))  );
		}
	}
	else if (pLVI->pPrescanRow->pRD)
	{
		SG_ERR_CHECK_RETURN(  sg_wc_readdir__row__get_attrbits(pCtx, pWcTx,
															   pLVI->pPrescanRow->pRD)  );
		if (pLVI->pPcRow_PC)
			SG_ERR_CHECK_RETURN(  sg_wc_attrbits__compute_effective_attrbits(pCtx,
																			 pWcTx->pDb->pAttrbitsData,
																			 pLVI->pPcRow_PC->ref_attrbits,
																			 *pLVI->pPrescanRow->pRD->pAttrbits,
																			 pAttrbits)  );
		else if (pLVI->pPrescanRow->pPcRow_Ref)
			SG_ERR_CHECK_RETURN(  sg_wc_attrbits__compute_effective_attrbits(pCtx,
																			 pWcTx->pDb->pAttrbitsData,
																			 pLVI->pPrescanRow->pPcRow_Ref->ref_attrbits,
																			 *pLVI->pPrescanRow->pRD->pAttrbits,
																			 pAttrbits)  );
		else if (pLVI->pPrescanRow->pTneRow)
			SG_ERR_CHECK_RETURN(  sg_wc_attrbits__compute_effective_attrbits(pCtx,
																			 pWcTx->pDb->pAttrbitsData,
																			 pLVI->pPrescanRow->pTneRow->p_d->attrbits,
																			 *pLVI->pPrescanRow->pRD->pAttrbits,
																			 pAttrbits)  );
		else
			*pAttrbits = *pLVI->pPrescanRow->pRD->pAttrbits;
	}
	else if (pLVI->pPrescanRow->pTneRow)
	{
		*pAttrbits = pLVI->pPrescanRow->pTneRow->p_d->attrbits;
	}
	else
	{
		*pAttrbits = SG_WC_ATTRBITS__ZERO;
	}

#if 1 && TRACE_WC_ATTRBITS
	SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
							   "GetCurrentAttrbits: yielded %x: %s\n",
							   (SG_uint32)(*pAttrbits),
							   SG_string__sz(pLVI->pStringEntryname))  );
#endif
}
/**
 * Get the baseline value of the content HID.
 *
 * We DO NOT attempt to substitute the merge-created-hid.
 * But we do for created-by-update items.
 *
 * You DO NOT own the returned result.
 * 
 */
void sg_wc_liveview_item__get_original_content_hid(SG_context * pCtx,
												   sg_wc_liveview_item * pLVI,
												   SG_wc_tx * pWcTx,
												   SG_bool bNoTSC,
												   const char ** ppszHidContent)
{
	sg_wc_db__pc_row__flags_net flags_net = SG_WC_DB__PC_ROW__FLAGS_NET__ZERO;

	if (SG_WC_PRESCAN_FLAGS__IS_RESERVED(pLVI->scan_flags_Live))
	{
		// We should not ever be called on a reserved item
		// because our caller shouldn't be trying to compare
		// HIDs (or whatever).
		//
		// TODO 2012/10/05 This should probably be an assert.
		SG_ERR_THROW2_RETURN(  SG_ERR_WC_RESERVED_ENTRYNAME,
							   (pCtx, "%s", SG_string__sz(pLVI->pStringEntryname))  );
	}

	SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow) );

	// try to get baseline value if present.
	// fallback to current value if not.

	if (pLVI->pTneRow_AlternateBaseline)
	{
		*ppszHidContent = pLVI->pTneRow_AlternateBaseline->p_d->pszHid;
		return;
	}

	if (pLVI->pPrescanRow->pTneRow)
	{
		*ppszHidContent = pLVI->pPrescanRow->pTneRow->p_d->pszHid;
		return;
	}

	if (SG_WC_PRESCAN_FLAGS__IS_CONTROLLED_SPARSE(pLVI->scan_flags_Live))
	{
		// TODO 2012/12/06 I think this only kicks in for __SPARSE + __ADD_SPECIAL_U
		// TODO            (otherwise, the above pTneRow would be present).

		if (pLVI->pPcRow_PC)
		{
			*ppszHidContent = pLVI->pPcRow_PC->p_d_sparse->pszHid;
			return;
		}
		else if (pLVI->pPrescanRow->pPcRow_Ref)
		{
			*ppszHidContent = pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse->pszHid;
			return;
		}
		else
		{
			SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
								   (pCtx, "GetOriginalContentHid: %s",
									SG_string__sz(pLVI->pStringEntryname))  );
		}
	}

	// TODO 2012/12/06 I think this can be simplified.
	// TODO            We only set pszHidMerge for plain files.
	if (pLVI->tneType != SG_TREENODEENTRY_TYPE_DIRECTORY)
	{
		// If this item was an added-by-update, then we can give them the
		// HID of the file that UPDATE created.
		SG_ERR_CHECK_RETURN(  sg_wc_liveview_item__get_flags_net(pCtx, pLVI, &flags_net)  );
		if (flags_net & SG_WC_DB__PC_ROW__FLAGS_NET__ADD_SPECIAL_U)
		{
			if (pLVI->pPcRow_PC)
			{
				if (pLVI->pPcRow_PC->pszHidMerge)
				{
					*ppszHidContent = pLVI->pPcRow_PC->pszHidMerge;
					return;
				}
			}
			else if (pLVI->pPrescanRow->pPcRow_Ref)
			{
				if (pLVI->pPrescanRow->pPcRow_Ref->pszHidMerge)
				{
					*ppszHidContent = pLVI->pPrescanRow->pPcRow_Ref->pszHidMerge;
					return;
				}
			}
		}
	}


#if TRACE_WC_TSC
	if (!bNoTSC)
		SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
								   "GetOriginalContentHid: looking up '%s'\n",
								   SG_string__sz(pLVI->pStringEntryname))  );
#endif

	if (!pLVI->pPrescanRow->pRD)
	{
		// Not likely to happen, but we can give a better(?) error message
		// than sg_wc_readdir__row__get_content_hid.
		SG_ERR_THROW2_RETURN(  SG_ERR_NOT_FOUND,
							   (pCtx, "No pRD for item '%s' in GetOriginalContentHid().",
								SG_string__sz(pLVI->pStringEntryname))  );
	}
	SG_ERR_CHECK_RETURN(  sg_wc_readdir__row__get_content_hid(pCtx, pWcTx,
															  pLVI->pPrescanRow->pRD,
															  bNoTSC,
															  ppszHidContent,
															  NULL)  );
}
/**
 * Get the current content HID and optionally the size.
 * (Note that the current HID is not usually defined for a directory.
 * And therefore the content size of a directory is not usually
 * defined either.)
 *
 */
void sg_wc_liveview_item__get_current_content_hid(SG_context * pCtx,
												  sg_wc_liveview_item * pLVI,
												  SG_wc_tx * pWcTx,
												  SG_bool bNoTSC,
												  char ** ppszHidContent,
												  SG_uint64 * pSize)
{
	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_RETURN(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "hid", &psz)  );
		if (psz)
		{
			// last overwrite-type operation used an HID.
#if TRACE_WC_LIE
			SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
									   "GetCurrentContentHid: using journal %s for: %s\n",
									   psz, SG_string__sz(pLVI->pStringEntryname))  );
#endif
			if (pSize)
				SG_ERR_CHECK_RETURN(  _fetch_size_of_blob(pCtx, pWcTx, psz, pSize)  );

			SG_ERR_CHECK_RETURN(  SG_strdup(pCtx, psz, ppszHidContent)  );
			return;
		}

		SG_ERR_CHECK_RETURN(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "file", &psz)  );
		if (psz)
		{
			// last overwrite-type operation used a TEMP file.

			SG_ERR_CHECK_RETURN(  sg_wc_compute_file_hid__sz(pCtx, pWcTx, psz, ppszHidContent, pSize)  );
			return;
		}

		SG_ERR_CHECK_RETURN(  SG_vhash__check__sz(pCtx, pLVI->queuedOverwrites.pvhContent, "target", &psz)  );
		if (psz)
		{
			// last overwrite-type operation gave us a SYMLINK-TARGET.
			// it is no problem to compute this, i'm just being
			// lazy since i'm not sure we need this.  
			SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
								   (pCtx,
									"GetCurrentContentHid: using journal: TODO compute HID of symlink target '%s' for: %s",
									psz, SG_string__sz(pLVI->pStringEntryname))  );
			// TODO also return size
		}

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

	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_RETURN(  SG_STRDUP(pCtx,
											pLVI->pPcRow_PC->p_d_sparse->pszHid,
											ppszHidContent)  );
			if (pSize)
				SG_ERR_CHECK_RETURN(  _fetch_size_of_blob(pCtx, pWcTx,
														  pLVI->pPcRow_PC->p_d_sparse->pszHid,
														  pSize)  );
		}
		else if (pLVI->pPrescanRow->pPcRow_Ref)
		{
			SG_ASSERT_RELEASE_RETURN( (pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse) );
			SG_ERR_CHECK_RETURN(  SG_STRDUP(pCtx,
											pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse->pszHid,
											ppszHidContent)  );
			if (pSize)
				SG_ERR_CHECK_RETURN(  _fetch_size_of_blob(pCtx, pWcTx,
														  pLVI->pPrescanRow->pPcRow_Ref->p_d_sparse->pszHid,
														  pSize)  );
		}
		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, "GetCurrentHid: unhandled case when sparse for '%s'.",
									SG_string__sz(pLVI->pStringEntryname))  );
		}
	}
	else if (pLVI->pPrescanRow->pRD)
	{
#if TRACE_WC_TSC
		if (!bNoTSC)
			SG_ERR_IGNORE(  SG_console(pCtx, SG_CS_STDERR,
									   "GetCurrentContentHid: looking up '%s'\n",
									   SG_string__sz(pLVI->pStringEntryname))  );
#endif
		SG_ERR_CHECK_RETURN(  sg_wc_readdir__row__get_content_hid__owned(pCtx, pWcTx,
																		 pLVI->pPrescanRow->pRD,
																		 bNoTSC,
																		 ppszHidContent,
																		 pSize)  );
	}
	else if (pLVI->pPrescanRow->pTneRow)
	{
		SG_ERR_CHECK_RETURN(  SG_STRDUP(pCtx, pLVI->pPrescanRow->pTneRow->p_d->pszHid, ppszHidContent)  );
		if (pSize)
			SG_ERR_CHECK_RETURN(  _fetch_size_of_blob(pCtx, pWcTx, pLVI->pPrescanRow->pTneRow->p_d->pszHid, pSize)  );
	}
	else
	{
		// perhaps an ADD-SPECIAL + DELETE
		// or an ADDED+LOST in an UPDATE ?
		SG_ERR_THROW2_RETURN(  SG_ERR_NOTIMPLEMENTED,
							   (pCtx, "GetCurrentHid: unhandled case for '%s'.",
								SG_string__sz(pLVI->pStringEntryname))  );
	}
}
/**
 * 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);
}
/**
 * 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);
}
static void do_upload__string(
    SG_context* pCtx,
    const char* pszUrl,
    const char* psz_method,
    SG_pathname* pPath,
    const char* psz_username,
    const char* psz_password,
    SG_string** ppstr
)
{
    SG_string* pstr = NULL;
    CFHTTPMessageRef myRequest = NULL;
    CFHTTPMessageRef myResponse = NULL;
    CFStringRef s_username = NULL;
    CFStringRef s_password = NULL;

    SG_ERR_CHECK(  make_request(pCtx, pszUrl, psz_method, NULL, &myRequest)  );
    SG_ERR_CHECK(  perform_upload_request__string(pCtx, myRequest, pPath, &myResponse, &pstr)  );
#if 0
    {
        CFDataRef d = CFHTTPMessageCopySerializedMessage(myResponse);
        fprintf(stderr, "%s\n", CFDataGetBytePtr(d));
        CFRelease(d);
    }
#endif
    UInt32 statusCode = CFHTTPMessageGetResponseStatusCode(myResponse);

    if (
        psz_username
        && psz_password
        && (statusCode == 401 || statusCode == 407)
    )
    {
        s_username = CFStringCreateWithCString(kCFAllocatorDefault, psz_username, kCFStringEncodingUTF8);
        s_password = CFStringCreateWithCString(kCFAllocatorDefault, psz_password, kCFStringEncodingUTF8);
        if (!CFHTTPMessageAddAuthentication(myRequest, myResponse, s_username, s_password, kCFHTTPAuthenticationSchemeDigest, FALSE))
        {
            SG_ERR_THROW2(SG_ERR_UNSPECIFIED, (pCtx, "CFHTTPMessageAddAuthentication failed"));
        }

#if 0
        {
            CFDataRef d = CFHTTPMessageCopySerializedMessage(myRequest);
            fprintf(stderr, "%s\n", CFDataGetBytePtr(d));
            CFRelease(d);
        }
#endif

        CFRelease(s_username);
        s_username = NULL;

        CFRelease(s_password);
        s_password = NULL;

        CFRelease(myResponse);
        myResponse = NULL;

        SG_STRING_NULLFREE(pCtx, pstr);
        SG_ERR_CHECK(  perform_upload_request__string(pCtx, myRequest, pPath, &myResponse, &pstr)  );

#if 0
        {
            CFDataRef d = CFHTTPMessageCopySerializedMessage(myResponse);
            fprintf(stderr, "%s\n", CFDataGetBytePtr(d));
            CFRelease(d);
        }
#endif
        statusCode = CFHTTPMessageGetResponseStatusCode(myResponse);
    }

    if (statusCode != 200)
    {
        SG_ERR_THROW2_RETURN(SG_ERR_SERVER_HTTP_ERROR, (pCtx, "%d", (int) statusCode));
    }

    *ppstr = pstr;
    pstr = NULL;

    /* fall through */

fail:
    if (s_username)
    {
        CFRelease(s_username);
        s_username = NULL;
    }
    if (s_password)
    {
        CFRelease(s_password);
        s_password = NULL;
    }
    if (myRequest)
    {
        CFRelease(myRequest);
        myRequest = NULL;
    }

    if (myResponse)
    {
        CFRelease(myResponse);
        myResponse = NULL;
    }

    SG_STRING_NULLFREE(pCtx, pstr);
}
Exemplo n.º 23
0
void SG_getopt__long(SG_context* pCtx,
					SG_getopt* os,
                    const SG_getopt_option* opts,
                    SG_uint32* optch, const char** optarg,
					SG_bool* bNoMoreOptions)
{
    const char *p;
    SG_int32 i;

    /* Let the calling program reset option processing. */
    if (os->reset) {
        os->place = "";
        os->ind = 1;
        os->reset = 0;
    }

    /*
     * We can be in one of two states: in the middle of processing a
     * run of short options, or about to process a new argument.
     * Since the second case can lead to the first one, handle that
     * one first.  */
    p = os->place;
    if (*p == '\0') {
        /* If we are interleaving, skip non-option arguments. */
        if (os->interleave) {
            while (os->ind < os->count_args && *os->paszArgs[os->ind] != '-')
                os->ind++;
            os->skip_end = os->ind;
        }
        if (os->ind >= os->count_args || *os->paszArgs[os->ind] != '-') {
            os->ind = os->skip_start;
            //return APR_EOF;
			*bNoMoreOptions = SG_TRUE;
			SG_ERR_THROW_RETURN(  SG_ERR_GETOPT_NO_MORE_OPTIONS  );
        }

        p = os->paszArgs[os->ind++] + 1;
        if (*p == '-' && p[1] != '\0') {        /* Long option */
            /* Search for the long option name in the caller's table. */
            SG_uint32 len = 0;

            p++;
            for (i = 0; ; i++) {
                if (opts[i].optch == 0)             /* No match */
					SG_ERR_THROW2_RETURN(  SG_ERR_GETOPT_BAD_ARG, (pCtx, "Invalid option: %s", p - 2)  );
                    //return serr(os, "invalid option", p - 2, APR_BADCH);

                if (opts[i].pStringName) {
                    len = strlen(opts[i].pStringName);
                    if (strncmp(p, opts[i].pStringName, len) == 0
                        && (p[len] == '\0' || p[len] == '='))
                        break;
                }
            }
            *optch = opts[i].optch;

            if (opts[i].has_arg) {
                if (p[len] == '=')             /* Argument inline */
                    *optarg = p + len + 1;
                else { 
                    if (os->ind >= os->count_args)   /* Argument missing */
						SG_ERR_THROW2_RETURN(  SG_ERR_GETOPT_BAD_ARG, (pCtx, "Missing argument:  %s", p - 2)  );
                        //return serr(os, "missing argument", p - 2, APR_BADARG);
                    else                       /* Argument in next arg */
                        *optarg = os->paszArgs[os->ind++];
                }
            } else {
                *optarg = NULL;
                if (p[len] == '=')
					SG_ERR_THROW2_RETURN(  SG_ERR_GETOPT_BAD_ARG, (pCtx, "Erroneous argument:  %s", p - 2)  );
                    //return serr(os, "erroneous argument", p - 2, APR_BADARG);
            }
            _sg_permute(os);
            //return APR_SUCCESS;
			*bNoMoreOptions = SG_FALSE;
			return;
        } else {
            if (*p == '-') {                 /* Bare "--"; we're done */
                _sg_permute(os);
                os->ind = os->skip_start;
                //return APR_EOF;
				*bNoMoreOptions = SG_TRUE;
				SG_ERR_THROW_RETURN(SG_ERR_GETOPT_NO_MORE_OPTIONS);
            }
            else 
                if (*p == '\0')                    /* Bare "-" is illegal */
					SG_ERR_THROW2_RETURN(  SG_ERR_GETOPT_BAD_ARG, (pCtx, "Invalid option:  %s", p)  );
                    //return serr(os, "invalid option", p, APR_BADCH);
        }
    }

    /*
     * Now we're in a run of short options, and *p is the next one.
     * Look for it in the caller's table.
     */
    for (i = 0; ; i++) {
        if (opts[i].optch == 0)                     /* No match */
			SG_ERR_THROW2_RETURN(  SG_ERR_GETOPT_BAD_ARG, (pCtx, "Invalid option character:  %c", *p)  );
            //return cerr(os, "invalid option character", *p, APR_BADCH);

        if (*p == opts[i].optch)
            break;
    }
    *optch = *p++;

    if (opts[i].has_arg) {
        if (*p != '\0')                         /* Argument inline */
            *optarg = p;
        else { 
            if (os->ind >= os->count_args)           /* Argument missing */
				SG_ERR_THROW2_RETURN(  SG_ERR_GETOPT_BAD_ARG, (pCtx, "Missing argument:  %c", *optch)  );
                //return cerr(os, "missing argument", *optch, APR_BADARG);
            else                               /* Argument in next arg */
                *optarg = os->paszArgs[os->ind++];
        }
        os->place = "";
    } else {
        *optarg = NULL;
        os->place = p;
    }

    _sg_permute(os);
	*bNoMoreOptions = SG_FALSE;
    return;
}