VirtualBox

source: vbox/trunk/src/bldprogs/scmsubversion.cpp@ 72602

Last change on this file since 72602 was 72602, checked in by vboxsync, 6 years ago

Linux/Address sanitizer: silence some warnings about memory leaks.
bugref:8019: Undefined behaviour sanitiser
Address sanitizer prints information about memory leaks when a binary exits.
Some things are hard to fix, some things we just do not bother. Document
that.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.9 KB
Line 
1/* $Id: scmsubversion.cpp 72602 2018-06-18 15:16:04Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager, Subversion Access.
4 */
5
6/*
7 * Copyright (C) 2010-2017 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
18#define SCM_WITH_DYNAMIC_LIB_SVN
19
20
21/*********************************************************************************************************************************
22* Header Files *
23*********************************************************************************************************************************/
24#include <iprt/assert.h>
25#include <iprt/ctype.h>
26#include <iprt/dir.h>
27#include <iprt/env.h>
28#include <iprt/err.h>
29#include <iprt/file.h>
30#include <iprt/getopt.h>
31#include <iprt/handle.h>
32#include <iprt/initterm.h>
33#include <iprt/ldr.h>
34#include <iprt/mem.h>
35#include <iprt/message.h>
36#include <iprt/param.h>
37#include <iprt/path.h>
38#include <iprt/pipe.h>
39#include <iprt/poll.h>
40#include <iprt/process.h>
41#include <iprt/stream.h>
42#include <iprt/string.h>
43
44#include "scm.h"
45
46#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && defined(SCM_WITH_SVN_HEADERS)
47# include <svn_client.h>
48#endif
49
50
51/*********************************************************************************************************************************
52* Defined Constants And Macros *
53*********************************************************************************************************************************/
54#ifdef SCM_WITH_DYNAMIC_LIB_SVN
55# if defined(RT_OS_WINDOWS) && defined(RT_ARCH_X86)
56# define APR_CALL __stdcall
57# define SVN_CALL /* __stdcall ?? */
58# else
59# define APR_CALL
60# define SVN_CALL
61# endif
62#endif
63#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS)
64# define SVN_ERR_MISC_CATEGORY_START 200000
65# define SVN_ERR_UNVERSIONED_RESOURCE (SVN_ERR_MISC_CATEGORY_START + 5)
66#endif
67
68
69/*********************************************************************************************************************************
70* Structures and Typedefs *
71*********************************************************************************************************************************/
72#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS)
73typedef int apr_status_t;
74typedef int64_t apr_time_t;
75typedef struct apr_pool_t apr_pool_t;
76typedef struct apr_hash_t apr_hash_t;
77typedef struct apr_hash_index_t apr_hash_index_t;
78typedef struct apr_array_header_t apr_array_header_t;
79
80
81typedef struct svn_error_t
82{
83 apr_status_t apr_err;
84 const char *_dbgr_message;
85 struct svn_error_t *_dbgr_child;
86 apr_pool_t *_dbgr_pool;
87 const char *_dbgr_file;
88 long _dbgr_line;
89} svn_error_t;
90typedef int svn_boolean_t;
91typedef long int svn_revnum_t;
92typedef struct svn_client_ctx_t svn_client_ctx_t;
93typedef enum svn_opt_revision_kind
94{
95 svn_opt_revision_unspecified = 0,
96 svn_opt_revision_number,
97 svn_opt_revision_date,
98 svn_opt_revision_committed,
99 svn_opt_revision_previous,
100 svn_opt_revision_base,
101 svn_opt_revision_working,
102 svn_opt_revision_head
103} svn_opt_revision_kind;
104typedef union svn_opt_revision_value_t
105{
106 svn_revnum_t number;
107 apr_time_t date;
108} svn_opt_revision_value_t;
109typedef struct svn_opt_revision_t
110{
111 svn_opt_revision_kind kind;
112 svn_opt_revision_value_t value;
113} svn_opt_revision_t;
114typedef enum svn_depth_t
115{
116 svn_depth_unknown = -2,
117 svn_depth_exclude,
118 svn_depth_empty,
119 svn_depth_files,
120 svn_depth_immediates,
121 svn_depth_infinity
122} svn_depth_t;
123
124#endif /* SCM_WITH_DYNAMIC_LIB_SVN && !SCM_WITH_SVN_HEADERS */
125
126
127/*********************************************************************************************************************************
128* Global Variables *
129*********************************************************************************************************************************/
130static char g_szSvnPath[RTPATH_MAX];
131static enum
132{
133 kScmSvnVersion_Ancient = 1,
134 kScmSvnVersion_1_6,
135 kScmSvnVersion_1_7,
136 kScmSvnVersion_1_8,
137 kScmSvnVersion_End
138} g_enmSvnVersion = kScmSvnVersion_Ancient;
139
140
141#ifdef SCM_WITH_DYNAMIC_LIB_SVN
142/** Set if all the function pointers are valid. */
143static bool g_fSvnFunctionPointersValid;
144/** @name SVN and APR imports.
145 * @{ */
146static apr_status_t (APR_CALL *g_pfnAprInitialize)(void);
147static apr_hash_index_t * (APR_CALL *g_pfnAprHashFirst)(apr_pool_t *pPool, apr_hash_t *pHashTab);
148static apr_hash_index_t * (APR_CALL *g_pfnAprHashNext)(apr_hash_index_t *pCurIdx);
149static void * (APR_CALL *g_pfnAprHashThisVal)(apr_hash_index_t *pHashIdx);
150static apr_pool_t * (SVN_CALL *g_pfnSvnPoolCreateEx)(apr_pool_t *pParent, void *pvAllocator);
151static void (APR_CALL *g_pfnAprPoolClear)(apr_pool_t *pPool);
152static void (APR_CALL *g_pfnAprPoolDestroy)(apr_pool_t *pPool);
153
154static svn_error_t * (SVN_CALL *g_pfnSvnClientCreateContext)(svn_client_ctx_t **ppCtx, apr_pool_t *pPool);
155static svn_error_t * (SVN_CALL *g_pfnSvnClientPropGet4)(apr_hash_t **ppHashProps, const char *pszPropName,
156 const char *pszTarget, const svn_opt_revision_t *pPeggedRev,
157 const svn_opt_revision_t *pRevision, svn_revnum_t *pActualRev,
158 svn_depth_t enmDepth, const apr_array_header_t *pChangeList,
159 svn_client_ctx_t *pCtx, apr_pool_t *pResultPool,
160 apr_pool_t *pScratchPool);
161/**@} */
162
163/** Cached APR pool. */
164static apr_pool_t *g_pSvnPool = NULL;
165/** Cached SVN client context. */
166static svn_client_ctx_t *g_pSvnClientCtx = NULL;
167/** Number of times the current context has been used. */
168static uint32_t g_cSvnClientCtxUsed = 0;
169
170#endif
171
172
173/*********************************************************************************************************************************
174* Internal Functions *
175*********************************************************************************************************************************/
176#ifdef SCM_WITH_DYNAMIC_LIB_SVN
177static void scmSvnFlushClientContextAndPool(void);
178#endif
179
180
181
182/**
183 * Callback that is call for each path to search.
184 */
185static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
186{
187 char *pszDst = (char *)pvUser1;
188 size_t cchDst = (size_t)pvUser2;
189 if (cchDst > cchPath)
190 {
191 memcpy(pszDst, pchPath, cchPath);
192 pszDst[cchPath] = '\0';
193#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
194 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
195#else
196 int rc = RTPathAppend(pszDst, cchDst, "svn");
197#endif
198 if ( RT_SUCCESS(rc)
199 && RTFileExists(pszDst))
200 return VINF_SUCCESS;
201 }
202 return VERR_TRY_AGAIN;
203}
204
205
206/**
207 * Reads from a pipe.
208 *
209 * @returns @a rc or other status code.
210 * @param rc The current status of the operation. Error status
211 * are preserved and returned.
212 * @param phPipeR Pointer to the pipe handle.
213 * @param pcbAllocated Pointer to the buffer size variable.
214 * @param poffCur Pointer to the buffer offset variable.
215 * @param ppszBuffer Pointer to the buffer pointer variable.
216 */
217static int rtProcProcessOutput(int rc, PRTPIPE phPipeR, size_t *pcbAllocated, size_t *poffCur, char **ppszBuffer,
218 RTPOLLSET hPollSet, uint32_t idPollSet)
219{
220 size_t cbRead;
221 char szTmp[_4K - 1];
222 for (;;)
223 {
224 int rc2 = RTPipeRead(*phPipeR, szTmp, sizeof(szTmp), &cbRead);
225 if (RT_SUCCESS(rc2) && cbRead)
226 {
227 /* Resize the buffer. */
228 if (*poffCur + cbRead >= *pcbAllocated)
229 {
230 if (*pcbAllocated >= _1G)
231 {
232 RTPollSetRemove(hPollSet, idPollSet);
233 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
234 *phPipeR = NIL_RTPIPE;
235 return RT_SUCCESS(rc) ? VERR_TOO_MUCH_DATA : rc;
236 }
237
238 size_t cbNew = *pcbAllocated ? *pcbAllocated * 2 : sizeof(szTmp) + 1;
239 Assert(*poffCur + cbRead < cbNew);
240 rc2 = RTStrRealloc(ppszBuffer, cbNew);
241 if (RT_FAILURE(rc2))
242 {
243 RTPollSetRemove(hPollSet, idPollSet);
244 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
245 *phPipeR = NIL_RTPIPE;
246 return RT_SUCCESS(rc) ? rc2 : rc;
247 }
248 *pcbAllocated = cbNew;
249 }
250
251 /* Append the new data, terminating it. */
252 memcpy(*ppszBuffer + *poffCur, szTmp, cbRead);
253 *poffCur += cbRead;
254 (*ppszBuffer)[*poffCur] = '\0';
255
256 /* Check for null terminators in the string. */
257 if (RT_SUCCESS(rc) && memchr(szTmp, '\0', cbRead))
258 rc = VERR_NO_TRANSLATION;
259
260 /* If we read a full buffer, try read some more. */
261 if (RT_SUCCESS(rc) && cbRead == sizeof(szTmp))
262 continue;
263 }
264 else if (rc2 != VINF_TRY_AGAIN)
265 {
266 if (RT_FAILURE(rc) && rc2 != VERR_BROKEN_PIPE)
267 rc = rc2;
268 RTPollSetRemove(hPollSet, idPollSet);
269 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
270 *phPipeR = NIL_RTPIPE;
271 }
272 return rc;
273 }
274}
275
276/** @name RTPROCEXEC_FLAGS_XXX - flags for RTProcExec and RTProcExecToString.
277 * @{ */
278/** Redirect /dev/null to standard input. */
279#define RTPROCEXEC_FLAGS_STDIN_NULL RT_BIT_32(0)
280/** Redirect standard output to /dev/null. */
281#define RTPROCEXEC_FLAGS_STDOUT_NULL RT_BIT_32(1)
282/** Redirect standard error to /dev/null. */
283#define RTPROCEXEC_FLAGS_STDERR_NULL RT_BIT_32(2)
284/** Redirect all standard output to /dev/null as well as directing /dev/null
285 * to standard input. */
286#define RTPROCEXEC_FLAGS_STD_NULL ( RTPROCEXEC_FLAGS_STDIN_NULL \
287 | RTPROCEXEC_FLAGS_STDOUT_NULL \
288 | RTPROCEXEC_FLAGS_STDERR_NULL)
289/** Mask containing the valid flags. */
290#define RTPROCEXEC_FLAGS_VALID_MASK UINT32_C(0x00000007)
291/** @} */
292
293/**
294 * Runs a process, collecting the standard output and/or standard error.
295 *
296 *
297 * @returns IPRT status code
298 * @retval VERR_NO_TRANSLATION if the output of the program isn't valid UTF-8
299 * or contains a nul character.
300 * @retval VERR_TOO_MUCH_DATA if the process produced too much data.
301 *
302 * @param pszExec Executable image to use to create the child process.
303 * @param papszArgs Pointer to an array of arguments to the child. The
304 * array terminated by an entry containing NULL.
305 * @param hEnv Handle to the environment block for the child.
306 * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX. The @a
307 * ppszStdOut and @a ppszStdErr parameters takes precedence
308 * over redirection flags.
309 * @param pStatus Where to return the status on success.
310 * @param ppszStdOut Where to return the text written to standard output. If
311 * NULL then standard output will not be collected and go
312 * to the standard output handle of the process.
313 * Free with RTStrFree, regardless of return status.
314 * @param ppszStdErr Where to return the text written to standard error. If
315 * NULL then standard output will not be collected and go
316 * to the standard error handle of the process.
317 * Free with RTStrFree, regardless of return status.
318 */
319int RTProcExecToString(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
320 PRTPROCSTATUS pStatus, char **ppszStdOut, char **ppszStdErr)
321{
322 int rc2;
323
324 /*
325 * Clear output arguments (no returning failure here, simply crash!).
326 */
327 AssertPtr(pStatus);
328 pStatus->enmReason = RTPROCEXITREASON_ABEND;
329 pStatus->iStatus = RTEXITCODE_FAILURE;
330 AssertPtrNull(ppszStdOut);
331 if (ppszStdOut)
332 *ppszStdOut = NULL;
333 AssertPtrNull(ppszStdOut);
334 if (ppszStdErr)
335 *ppszStdErr = NULL;
336
337 /*
338 * Check input arguments.
339 */
340 AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
341
342 /*
343 * Do we need a standard input bitbucket?
344 */
345 int rc = VINF_SUCCESS;
346 PRTHANDLE phChildStdIn = NULL;
347 RTHANDLE hChildStdIn;
348 hChildStdIn.enmType = RTHANDLETYPE_FILE;
349 hChildStdIn.u.hFile = NIL_RTFILE;
350 if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
351 {
352 phChildStdIn = &hChildStdIn;
353 rc = RTFileOpenBitBucket(&hChildStdIn.u.hFile, RTFILE_O_READ);
354 }
355
356 /*
357 * Create the output pipes / bitbuckets.
358 */
359 RTPIPE hPipeStdOutR = NIL_RTPIPE;
360 PRTHANDLE phChildStdOut = NULL;
361 RTHANDLE hChildStdOut;
362 hChildStdOut.enmType = RTHANDLETYPE_PIPE;
363 hChildStdOut.u.hPipe = NIL_RTPIPE;
364 if (ppszStdOut && RT_SUCCESS(rc))
365 {
366 phChildStdOut = &hChildStdOut;
367 rc = RTPipeCreate(&hPipeStdOutR, &hChildStdOut.u.hPipe, 0 /*fFlags*/);
368 }
369 else if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
370 {
371 phChildStdOut = &hChildStdOut;
372 hChildStdOut.enmType = RTHANDLETYPE_FILE;
373 hChildStdOut.u.hFile = NIL_RTFILE;
374 rc = RTFileOpenBitBucket(&hChildStdOut.u.hFile, RTFILE_O_WRITE);
375 }
376
377 RTPIPE hPipeStdErrR = NIL_RTPIPE;
378 PRTHANDLE phChildStdErr = NULL;
379 RTHANDLE hChildStdErr;
380 hChildStdErr.enmType = RTHANDLETYPE_PIPE;
381 hChildStdErr.u.hPipe = NIL_RTPIPE;
382 if (ppszStdErr && RT_SUCCESS(rc))
383 {
384 phChildStdErr = &hChildStdErr;
385 rc = RTPipeCreate(&hPipeStdErrR, &hChildStdErr.u.hPipe, 0 /*fFlags*/);
386 }
387 else if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
388 {
389 phChildStdErr = &hChildStdErr;
390 hChildStdErr.enmType = RTHANDLETYPE_FILE;
391 hChildStdErr.u.hFile = NIL_RTFILE;
392 rc = RTFileOpenBitBucket(&hChildStdErr.u.hFile, RTFILE_O_WRITE);
393 }
394
395 if (RT_SUCCESS(rc))
396 {
397 RTPOLLSET hPollSet;
398 rc = RTPollSetCreate(&hPollSet);
399 if (RT_SUCCESS(rc))
400 {
401 if (hPipeStdOutR != NIL_RTPIPE && RT_SUCCESS(rc))
402 rc = RTPollSetAddPipe(hPollSet, hPipeStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 1);
403 if (hPipeStdErrR != NIL_RTPIPE)
404 rc = RTPollSetAddPipe(hPollSet, hPipeStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 2);
405 }
406 if (RT_SUCCESS(rc))
407 {
408 /*
409 * Create the process.
410 */
411 RTPROCESS hProc;
412 rc = RTProcCreateEx(pszExec,
413 papszArgs,
414 hEnv,
415 0 /*fFlags*/,
416 NULL /*phStdIn*/,
417 phChildStdOut,
418 phChildStdErr,
419 NULL /*pszAsUser*/,
420 NULL /*pszPassword*/,
421 &hProc);
422 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
423 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
424
425 if (RT_SUCCESS(rc))
426 {
427 /*
428 * Process output and wait for the process to finish.
429 */
430 size_t cbStdOut = 0;
431 size_t offStdOut = 0;
432 size_t cbStdErr = 0;
433 size_t offStdErr = 0;
434 for (;;)
435 {
436 if (hPipeStdOutR != NIL_RTPIPE)
437 rc = rtProcProcessOutput(rc, &hPipeStdOutR, &cbStdOut, &offStdOut, ppszStdOut, hPollSet, 1);
438 if (hPipeStdErrR != NIL_RTPIPE)
439 rc = rtProcProcessOutput(rc, &hPipeStdErrR, &cbStdErr, &offStdErr, ppszStdErr, hPollSet, 2);
440 if (hPipeStdOutR == NIL_RTPIPE && hPipeStdErrR == NIL_RTPIPE)
441 break;
442
443 if (hProc != NIL_RTPROCESS)
444 {
445 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_NOBLOCK, pStatus);
446 if (rc2 != VERR_PROCESS_RUNNING)
447 {
448 if (RT_FAILURE(rc2))
449 rc = rc2;
450 hProc = NIL_RTPROCESS;
451 }
452 }
453
454 rc2 = RTPoll(hPollSet, 10000, NULL, NULL);
455 Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT);
456 }
457
458 if (RT_SUCCESS(rc))
459 {
460 if ( (ppszStdOut && *ppszStdOut && !RTStrIsValidEncoding(*ppszStdOut))
461 || (ppszStdErr && *ppszStdErr && !RTStrIsValidEncoding(*ppszStdErr)) )
462 rc = VERR_NO_TRANSLATION;
463 }
464
465 /*
466 * No more output, just wait for it to finish.
467 */
468 if (hProc != NIL_RTPROCESS)
469 {
470 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
471 if (RT_FAILURE(rc2))
472 rc = rc2;
473 }
474 }
475 RTPollSetDestroy(hPollSet);
476 }
477 }
478
479 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
480 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
481 rc2 = RTHandleClose(&hChildStdIn); AssertRC(rc2);
482 rc2 = RTPipeClose(hPipeStdErrR); AssertRC(rc2);
483 rc2 = RTPipeClose(hPipeStdOutR); AssertRC(rc2);
484 return rc;
485}
486
487
488/**
489 * Runs a process, waiting for it to complete.
490 *
491 * @returns IPRT status code
492 *
493 * @param pszExec Executable image to use to create the child process.
494 * @param papszArgs Pointer to an array of arguments to the child. The
495 * array terminated by an entry containing NULL.
496 * @param hEnv Handle to the environment block for the child.
497 * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX.
498 * @param pStatus Where to return the status on success.
499 */
500int RTProcExec(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
501 PRTPROCSTATUS pStatus)
502{
503 int rc;
504
505 /*
506 * Clear output argument (no returning failure here, simply crash!).
507 */
508 AssertPtr(pStatus);
509 pStatus->enmReason = RTPROCEXITREASON_ABEND;
510 pStatus->iStatus = RTEXITCODE_FAILURE;
511
512 /*
513 * Check input arguments.
514 */
515 AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
516
517 /*
518 * Set up /dev/null redirections.
519 */
520 PRTHANDLE aph[3] = { NULL, NULL, NULL };
521 RTHANDLE ah[3];
522 for (uint32_t i = 0; i < 3; i++)
523 {
524 ah[i].enmType = RTHANDLETYPE_FILE;
525 ah[i].u.hFile = NIL_RTFILE;
526 }
527 rc = VINF_SUCCESS;
528 if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
529 {
530 aph[0] = &ah[0];
531 rc = RTFileOpenBitBucket(&ah[0].u.hFile, RTFILE_O_READ);
532 }
533 if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
534 {
535 aph[1] = &ah[1];
536 rc = RTFileOpenBitBucket(&ah[1].u.hFile, RTFILE_O_WRITE);
537 }
538 if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
539 {
540 aph[2] = &ah[2];
541 rc = RTFileOpenBitBucket(&ah[2].u.hFile, RTFILE_O_WRITE);
542 }
543
544 /*
545 * Create the process.
546 */
547 RTPROCESS hProc = NIL_RTPROCESS;
548 if (RT_SUCCESS(rc))
549 rc = RTProcCreateEx(pszExec,
550 papszArgs,
551 hEnv,
552 0 /*fFlags*/,
553 aph[0],
554 aph[1],
555 aph[2],
556 NULL /*pszAsUser*/,
557 NULL /*pszPassword*/,
558 &hProc);
559
560 for (uint32_t i = 0; i < 3; i++)
561 RTFileClose(ah[i].u.hFile);
562
563 if (RT_SUCCESS(rc))
564 rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
565 return rc;
566}
567
568
569
570/**
571 * Executes SVN and gets the output.
572 *
573 * Standard error is suppressed.
574 *
575 * @returns VINF_SUCCESS if the command executed successfully.
576 * @param pState The rewrite state to work on. Can be NULL.
577 * @param papszArgs The SVN argument.
578 * @param fNormalFailureOk Whether normal failure is ok.
579 * @param ppszStdOut Where to return the output on success.
580 */
581static int scmSvnRunAndGetOutput(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk, char **ppszStdOut)
582{
583 *ppszStdOut = NULL;
584
585#ifdef SCM_WITH_DYNAMIC_LIB_SVN
586 scmSvnFlushClientContextAndPool();
587#endif
588
589 char *pszCmdLine = NULL;
590 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
591 if (RT_FAILURE(rc))
592 return rc;
593 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
594
595 RTPROCSTATUS Status;
596 rc = RTProcExecToString(g_szSvnPath, papszArgs, RTENV_DEFAULT,
597 RTPROCEXEC_FLAGS_STD_NULL, &Status, ppszStdOut, NULL);
598
599 if ( RT_SUCCESS(rc)
600 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
601 || Status.iStatus != 0) )
602 {
603 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
604 RTMsgError("%s: %s -> %s %u\n",
605 pState ? pState->pszFilename : "<NONE>", pszCmdLine,
606 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
607 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
608 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
609 : "abducted by alien",
610 Status.iStatus);
611 rc = VERR_GENERAL_FAILURE;
612 }
613 else if (RT_FAILURE(rc))
614 {
615 if (pState)
616 RTMsgError("%s: executing: %s => %Rrc\n", pState->pszFilename, pszCmdLine, rc);
617 else
618 RTMsgError("executing: %s => %Rrc\n", pszCmdLine, rc);
619 }
620
621 if (RT_FAILURE(rc))
622 {
623 RTStrFree(*ppszStdOut);
624 *ppszStdOut = NULL;
625 }
626 RTStrFree(pszCmdLine);
627 return rc;
628}
629
630
631/**
632 * Executes SVN.
633 *
634 * Standard error and standard output is suppressed.
635 *
636 * @returns VINF_SUCCESS if the command executed successfully.
637 * @param pState The rewrite state to work on.
638 * @param papszArgs The SVN argument.
639 * @param fNormalFailureOk Whether normal failure is ok.
640 */
641static int scmSvnRun(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk)
642{
643#ifdef SCM_WITH_DYNAMIC_LIB_SVN
644 scmSvnFlushClientContextAndPool();
645#endif
646
647 char *pszCmdLine = NULL;
648 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
649 if (RT_FAILURE(rc))
650 return rc;
651 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
652
653 /* Lazy bird uses RTProcExec. */
654 RTPROCSTATUS Status;
655 rc = RTProcExec(g_szSvnPath, papszArgs, RTENV_DEFAULT, RTPROCEXEC_FLAGS_STD_NULL, &Status);
656
657 if ( RT_SUCCESS(rc)
658 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
659 || Status.iStatus != 0) )
660 {
661 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
662 RTMsgError("%s: %s -> %s %u\n",
663 pState->pszFilename,
664 pszCmdLine,
665 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
666 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
667 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
668 : "abducted by alien",
669 Status.iStatus);
670 rc = VERR_GENERAL_FAILURE;
671 }
672 else if (RT_FAILURE(rc))
673 RTMsgError("%s: %s -> %Rrc\n", pState->pszFilename, pszCmdLine, rc);
674
675 RTStrFree(pszCmdLine);
676 return rc;
677}
678
679
680#ifdef SCM_WITH_DYNAMIC_LIB_SVN
681/**
682 * Attempts to resolve the necessary subversion and apache portable runtime APIs
683 * we require dynamically.
684 *
685 * Will set all global function pointers and g_fSvnFunctionPointersValid to true
686 * on success.
687 */
688static void scmSvnTryResolveFunctions(void)
689{
690 char szPath[RTPATH_MAX];
691 int rc = RTStrCopy(szPath, sizeof(szPath), g_szSvnPath);
692 if (RT_SUCCESS(rc))
693 {
694 RTPathStripFilename(szPath);
695 char *pszEndPath = strchr(szPath, '\0');
696# ifdef RT_OS_WINDOWS
697 RTPathChangeToDosSlashes(szPath, false);
698# endif
699
700 /*
701 * Try various prefixes/suffxies/locations.
702 */
703 static struct
704 {
705 const char *pszPrefix;
706 const char *pszSuffix;
707 } const s_aVariations[] =
708 {
709# ifdef RT_OS_WINDOWS
710 { "SlikSvn-lib", "-1.dll" }, /* SlikSVN */
711 { "lib", "-1.dll" }, /* Win32Svn,CollabNet,++ */
712# elif defined(RT_OS_DARWIN)
713 { "../lib/lib", "-1.dylib" },
714# else
715 { "../lib/lib", ".so" },
716 { "../lib/lib", "-1.so" },
717# ifdef RT_ARCH_X86
718 { "../lib/i386-linux-gnu/lib", ".so" },
719 { "../lib/i386-linux-gnu/lib", "-1.so" },
720# else
721 { "../lib/x86_64-linux-gnu/lib", ".so" },
722 { "../lib/x86_64-linux-gnu/lib", "-1.so" },
723# endif
724# endif
725 };
726 for (unsigned iVar = 0; iVar < RT_ELEMENTS(s_aVariations); iVar++)
727 {
728 /*
729 * Try load the svn_client library ...
730 */
731 static const char * const s_apszLibraries[] = { "svn_client", "svn_subr", "apr" };
732 RTLDRMOD ahMods[RT_ELEMENTS(s_apszLibraries)] = { NIL_RTLDRMOD, NIL_RTLDRMOD, NIL_RTLDRMOD };
733
734 rc = VINF_SUCCESS;
735 unsigned iLib;
736 for (iLib = 0; iLib < RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc); iLib++)
737 {
738 static const char * const s_apszSuffixes[] = { "", ".0", ".1" };
739 for (unsigned iSuff = 0; iSuff < RT_ELEMENTS(s_apszSuffixes); iSuff++)
740 {
741 *pszEndPath = '\0';
742 rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar].pszPrefix);
743 if (RT_SUCCESS(rc))
744 rc = RTStrCat(szPath, sizeof(szPath), s_apszLibraries[iLib]);
745 if (RT_SUCCESS(rc))
746 rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar].pszSuffix);
747 if (RT_SUCCESS(rc))
748 rc = RTStrCat(szPath, sizeof(szPath), s_apszSuffixes[iSuff]);
749 if (RT_SUCCESS(rc))
750 {
751# ifdef RT_OS_WINDOWS
752 RTPathChangeToDosSlashes(pszEndPath, false);
753# endif
754 rc = RTLdrLoadEx(szPath, &ahMods[iLib], RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR , NULL);
755#ifdef VBOX_WITH_GCC_SANITIZER
756 if (RT_SUCCESS(rc))
757 __lsan_ignore_object(ahMods[iLib]);
758#endif
759 if (RT_SUCCESS(rc))
760 break;
761 }
762 }
763 }
764 if (iLib == RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc))
765 {
766 static const struct
767 {
768 unsigned iLib;
769 const char *pszSymbol;
770 PFNRT *ppfn;
771 } s_aSymbols[] =
772 {
773 { 2, "apr_initialize", (PFNRT *)&g_pfnAprInitialize },
774 { 2, "apr_hash_first", (PFNRT *)&g_pfnAprHashFirst },
775 { 2, "apr_hash_next", (PFNRT *)&g_pfnAprHashNext },
776 { 2, "apr_hash_this_val", (PFNRT *)&g_pfnAprHashThisVal },
777 { 1, "svn_pool_create_ex", (PFNRT *)&g_pfnSvnPoolCreateEx },
778 { 2, "apr_pool_clear", (PFNRT *)&g_pfnAprPoolClear },
779 { 2, "apr_pool_destroy", (PFNRT *)&g_pfnAprPoolDestroy },
780 { 0, "svn_client_create_context", (PFNRT *)&g_pfnSvnClientCreateContext },
781 { 0, "svn_client_propget4", (PFNRT *)&g_pfnSvnClientPropGet4 },
782 };
783 for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++)
784 {
785 rc = RTLdrGetSymbol(ahMods[s_aSymbols[i].iLib], s_aSymbols[i].pszSymbol,
786 (void **)(uintptr_t)s_aSymbols[i].ppfn);
787 if (RT_FAILURE(rc))
788 {
789 ScmVerbose(NULL, 0, "Failed to resolve '%s' in '%s'",
790 s_aSymbols[i].pszSymbol, s_apszLibraries[s_aSymbols[i].iLib]);
791 break;
792 }
793 }
794 if (RT_SUCCESS(rc))
795 {
796 apr_status_t rcApr = g_pfnAprInitialize();
797 if (rcApr == 0)
798 {
799 ScmVerbose(NULL, 1, "Found subversion APIs.\n");
800 g_fSvnFunctionPointersValid = true;
801 }
802 else
803 {
804 ScmVerbose(NULL, 0, "apr_initialize failed: %#x (%d)\n", rcApr, rcApr);
805 AssertMsgFailed(("%#x (%d)\n", rc, rc));
806 }
807 return;
808 }
809 }
810
811 while (iLib-- > 0)
812 RTLdrClose(ahMods[iLib]);
813 }
814 }
815}
816#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
817
818
819/**
820 * Finds the svn binary, updating g_szSvnPath and g_enmSvnVersion.
821 */
822static void scmSvnFindSvnBinary(PSCMRWSTATE pState)
823{
824 /* Already been called? */
825 if (g_szSvnPath[0] != '\0')
826 return;
827
828 /*
829 * Locate it.
830 */
831 /** @todo code page fun... */
832#ifdef RT_OS_WINDOWS
833 const char *pszEnvVar = RTEnvGet("Path");
834#else
835 const char *pszEnvVar = RTEnvGet("PATH");
836#endif
837 if (pszEnvVar)
838 {
839#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
840 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
841#else
842 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
843#endif
844 if (RT_FAILURE(rc))
845 strcpy(g_szSvnPath, "svn");
846 }
847 else
848 strcpy(g_szSvnPath, "svn");
849
850 /*
851 * Check the version.
852 */
853 const char *apszArgs[] = { g_szSvnPath, "--version", "--quiet", NULL };
854 char *pszVersion;
855 int rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszVersion);
856 if (RT_SUCCESS(rc))
857 {
858 char *pszStripped = RTStrStrip(pszVersion);
859 if (RTStrVersionCompare(pszStripped, "1.8") >= 0)
860 g_enmSvnVersion = kScmSvnVersion_1_8;
861 else if (RTStrVersionCompare(pszStripped, "1.7") >= 0)
862 g_enmSvnVersion = kScmSvnVersion_1_7;
863 else if (RTStrVersionCompare(pszStripped, "1.6") >= 0)
864 g_enmSvnVersion = kScmSvnVersion_1_6;
865 else
866 g_enmSvnVersion = kScmSvnVersion_Ancient;
867 RTStrFree(pszVersion);
868 }
869 else
870 g_enmSvnVersion = kScmSvnVersion_Ancient;
871
872#ifdef SCM_WITH_DYNAMIC_LIB_SVN
873 /*
874 * If we got version 1.8 or later, try see if we can locate a few of the
875 * simpler SVN APIs.
876 */
877 g_fSvnFunctionPointersValid = false;
878 if (g_enmSvnVersion >= kScmSvnVersion_1_8)
879 scmSvnTryResolveFunctions();
880#endif
881}
882
883
884/**
885 * Construct a dot svn filename for the file being rewritten.
886 *
887 * @returns IPRT status code.
888 * @param pState The rewrite state (for the name).
889 * @param pszDir The directory, including ".svn/".
890 * @param pszSuff The filename suffix.
891 * @param pszDst The output buffer. RTPATH_MAX in size.
892 */
893static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
894{
895 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
896 RTPathStripFilename(pszDst);
897
898 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
899 if (RT_SUCCESS(rc))
900 {
901 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
902 if (RT_SUCCESS(rc))
903 {
904 size_t cchDst = strlen(pszDst);
905 size_t cchSuff = strlen(pszSuff);
906 if (cchDst + cchSuff < RTPATH_MAX)
907 {
908 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
909 return VINF_SUCCESS;
910 }
911 else
912 rc = VERR_BUFFER_OVERFLOW;
913 }
914 }
915 return rc;
916}
917
918/**
919 * Interprets the specified string as decimal numbers.
920 *
921 * @returns true if parsed successfully, false if not.
922 * @param pch The string (not terminated).
923 * @param cch The string length.
924 * @param pu Where to return the value.
925 */
926static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
927{
928 size_t u = 0;
929 while (cch-- > 0)
930 {
931 char ch = *pch++;
932 if (ch < '0' || ch > '9')
933 return false;
934 u *= 10;
935 u += ch - '0';
936 }
937 *pu = u;
938 return true;
939}
940
941
942#ifdef SCM_WITH_DYNAMIC_LIB_SVN
943
944/**
945 * Wrapper around RTPathAbs.
946 * @returns Same as RTPathAbs.
947 * @param pszPath The relative path.
948 * @param pszAbsPath Where to return the absolute path.
949 * @param cbAbsPath Size of the @a pszAbsPath buffer.
950 */
951static int scmSvnAbsPath(const char *pszPath, char *pszAbsPath, size_t cbAbsPath)
952{
953 int rc = RTPathAbs(pszPath, pszAbsPath, cbAbsPath);
954# if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
955 if (RT_SUCCESS(rc))
956 {
957 RTPathChangeToUnixSlashes(pszAbsPath, true /*fForce*/);
958 /* To avoid: svn: E235000: In file '..\..\..\subversion\libsvn_client\prop_commands.c' line 796: assertion failed (svn_dirent_is_absolute(target)) */
959 if (pszAbsPath[1] == ':')
960 pszAbsPath[0] = RT_C_TO_UPPER(pszAbsPath[0]);
961 }
962# endif
963 return rc;
964}
965
966
967/**
968 * Gets a client context and pool.
969 *
970 * This implements caching.
971 *
972 * @returns IPRT status code.
973 * @param ppCtx Where to return the context
974 * @param ppPool Where to return the pool.
975 */
976static int scmSvnGetClientContextAndPool(svn_client_ctx_t **ppCtx, apr_pool_t **ppPool)
977{
978 /*
979 * Use cached if present.
980 */
981 if (g_pSvnClientCtx && g_pSvnPool)
982 {
983 g_cSvnClientCtxUsed++;
984 *ppCtx = g_pSvnClientCtx;
985 *ppPool = g_pSvnPool;
986 return VINF_SUCCESS;
987 }
988 Assert(!g_pSvnClientCtx);
989 Assert(!g_pSvnPool);
990
991 /*
992 * Create new pool and context.
993 */
994 apr_pool_t *pPool = g_pfnSvnPoolCreateEx(NULL, NULL);
995 if (pPool)
996 {
997 svn_client_ctx_t *pCtx = NULL;
998 svn_error_t *pErr = g_pfnSvnClientCreateContext(&pCtx, pPool);
999 if (!pErr)
1000 {
1001 g_cSvnClientCtxUsed = 1;
1002 g_pSvnClientCtx = *ppCtx = pCtx;
1003 g_pSvnPool = *ppPool = pPool;
1004 return VINF_SUCCESS;
1005 }
1006 g_pfnAprPoolDestroy(pPool);
1007 }
1008
1009 *ppCtx = NULL;
1010 *ppPool = NULL;
1011 return VERR_GENERAL_FAILURE;
1012}
1013
1014
1015/**
1016 * Puts back a client context and pool after use.
1017 *
1018 * @param pCtx The context.
1019 * @param pPool The pool.
1020 * @param fFlush Whether to flush it.
1021 */
1022static void scmSvnPutClientContextAndPool(svn_client_ctx_t *pCtx, apr_pool_t *pPool, bool fFlush)
1023{
1024 if (fFlush || g_cSvnClientCtxUsed > 4096) /* Disable this to force new context every time. */
1025 {
1026 g_pfnAprPoolDestroy(pPool);
1027 g_pSvnPool = NULL;
1028 g_pSvnClientCtx = NULL;
1029 }
1030 RT_NOREF(pCtx, fFlush);
1031}
1032
1033
1034/**
1035 * Flushes the cached client context and pool
1036 */
1037static void scmSvnFlushClientContextAndPool(void)
1038{
1039 if (g_pSvnPool)
1040 scmSvnPutClientContextAndPool(g_pSvnClientCtx, g_pSvnPool, true /*fFlush*/);
1041 Assert(!g_pSvnPool);
1042}
1043
1044
1045/**
1046 * Checks if @a pszPath exists in the current WC.
1047 *
1048 * @returns true, false or -1. In the latter case, please use the fallback.
1049 * @param pszPath Path to the object that should be investigated.
1050 */
1051static int scmSvnIsObjectInWorkingCopy(const char *pszPath)
1052{
1053 /* svn_client_propget4 and later requires absolute target path. */
1054 char szAbsPath[RTPATH_MAX];
1055 int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
1056 if (RT_SUCCESS(rc))
1057 {
1058 apr_pool_t *pPool;
1059 svn_client_ctx_t *pCtx = NULL;
1060 rc = scmSvnGetClientContextAndPool(&pCtx, &pPool);
1061 if (RT_SUCCESS(rc))
1062 {
1063 /* Make the call. */
1064 apr_hash_t *pHash = NULL;
1065 svn_opt_revision_t Rev;
1066 RT_ZERO(Rev);
1067 Rev.kind = svn_opt_revision_working;
1068 Rev.value.number = -1L;
1069 svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, "svn:no-such-property", szAbsPath, &Rev, &Rev,
1070 NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/,
1071 pCtx, pPool, pPool);
1072 if (!pErr)
1073 rc = true;
1074 else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
1075 rc = false;
1076
1077 scmSvnPutClientContextAndPool(pCtx, pPool, false);
1078 }
1079 }
1080 return rc;
1081}
1082
1083#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
1084
1085
1086/**
1087 * Checks if the file we're operating on is part of a SVN working copy.
1088 *
1089 * @returns true if it is, false if it isn't or we cannot tell.
1090 * @param pState The rewrite state to work on. Will use the
1091 * fIsInSvnWorkingCopy member for caching the result.
1092 */
1093bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
1094{
1095 /*
1096 * We don't ask SVN twice as that's expensive.
1097 */
1098 if (pState->fIsInSvnWorkingCopy != 0)
1099 return pState->fIsInSvnWorkingCopy > 0;
1100
1101#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1102 if (g_fSvnFunctionPointersValid)
1103 {
1104 int rc = scmSvnIsObjectInWorkingCopy(pState->pszFilename);
1105 if (rc == (int)true || rc == (int)false)
1106 {
1107 pState->fIsInSvnWorkingCopy = rc == (int)true ? 1 : -1;
1108 return rc == (int)true;
1109 }
1110 }
1111
1112 /* Fallback: */
1113#endif
1114 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1115 {
1116 /*
1117 * Hack: check if the .svn/text-base/<file>.svn-base file exists.
1118 */
1119 char szPath[RTPATH_MAX];
1120 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
1121 if (RT_SUCCESS(rc))
1122 {
1123 if (RTFileExists(szPath))
1124 {
1125 pState->fIsInSvnWorkingCopy = 1;
1126 return true;
1127 }
1128 }
1129 }
1130 else
1131 {
1132 const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pState->pszFilename, NULL };
1133 char *pszValue;
1134 int rc = scmSvnRunAndGetOutput(pState, apszArgs, true, &pszValue);
1135 if (RT_SUCCESS(rc))
1136 {
1137 RTStrFree(pszValue);
1138 pState->fIsInSvnWorkingCopy = 1;
1139 return true;
1140 }
1141 }
1142 pState->fIsInSvnWorkingCopy = -1;
1143 return false;
1144}
1145
1146
1147/**
1148 * Checks if the specified directory is part of a SVN working copy.
1149 *
1150 * @returns true if it is, false if it isn't or we cannot tell.
1151 * @param pszDir The directory in question.
1152 */
1153bool ScmSvnIsDirInWorkingCopy(const char *pszDir)
1154{
1155#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1156 if (g_fSvnFunctionPointersValid)
1157 {
1158 int rc = scmSvnIsObjectInWorkingCopy(pszDir);
1159 if (rc == (int)true || rc == (int)false)
1160 return rc == (int)true;
1161 }
1162
1163 /* Fallback: */
1164#endif
1165 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1166 {
1167 /*
1168 * Hack: check if the .svn/ dir exists.
1169 */
1170 char szPath[RTPATH_MAX];
1171 int rc = RTPathJoin(szPath, sizeof(szPath), pszDir, ".svn");
1172 if (RT_SUCCESS(rc))
1173 return RTDirExists(szPath);
1174 }
1175 else
1176 {
1177 const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pszDir, NULL };
1178 char *pszValue;
1179 int rc = scmSvnRunAndGetOutput(NULL, apszArgs, true, &pszValue);
1180 if (RT_SUCCESS(rc))
1181 {
1182 RTStrFree(pszValue);
1183 return true;
1184 }
1185 }
1186 return false;
1187}
1188
1189
1190#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1191/**
1192 * Checks if @a pszPath exists in the current WC.
1193 *
1194 * @returns IPRT status code - VERR_NOT_SUPPORT if fallback should be attempted.
1195 * @param pszPath Path to the object that should be investigated.
1196 * @param pszProperty The property name.
1197 * @param ppszValue Where to return the property value. Optional.
1198 */
1199static int scmSvnQueryPropertyUsingApi(const char *pszPath, const char *pszProperty, char **ppszValue)
1200{
1201 /* svn_client_propget4 and later requires absolute target path. */
1202 char szAbsPath[RTPATH_MAX];
1203 int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
1204 if (RT_SUCCESS(rc))
1205 {
1206 apr_pool_t *pPool;
1207 svn_client_ctx_t *pCtx = NULL;
1208 rc = scmSvnGetClientContextAndPool(&pCtx, &pPool);
1209 if (RT_SUCCESS(rc))
1210 {
1211 /* Make the call. */
1212 apr_hash_t *pHash = NULL;
1213 svn_opt_revision_t Rev;
1214 RT_ZERO(Rev);
1215 Rev.kind = svn_opt_revision_working;
1216 Rev.value.number = -1L;
1217 svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, pszProperty, szAbsPath, &Rev, &Rev,
1218 NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/,
1219 pCtx, pPool, pPool);
1220 if (!pErr)
1221 {
1222 /* Get the first value, if any. */
1223 rc = VERR_NOT_FOUND;
1224 apr_hash_index_t *pHashIdx = g_pfnAprHashFirst(pPool, pHash);
1225 if (pHashIdx)
1226 {
1227 const char **ppszFirst = (const char **)g_pfnAprHashThisVal(pHashIdx);
1228 if (ppszFirst && *ppszFirst)
1229 {
1230 if (ppszValue)
1231 rc = RTStrDupEx(ppszValue, *ppszFirst);
1232 else
1233 rc = VINF_SUCCESS;
1234 }
1235 }
1236 }
1237 else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
1238 rc = VERR_INVALID_STATE;
1239 else
1240 rc = VERR_GENERAL_FAILURE;
1241
1242 scmSvnPutClientContextAndPool(pCtx, pPool, false);
1243 }
1244 }
1245 return rc;
1246}
1247#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
1248
1249
1250/**
1251 * Queries the value of an SVN property.
1252 *
1253 * This will automatically adjust for scheduled changes.
1254 *
1255 * @returns IPRT status code.
1256 * @retval VERR_INVALID_STATE if not a SVN WC file.
1257 * @retval VERR_NOT_FOUND if the property wasn't found.
1258 * @param pState The rewrite state to work on.
1259 * @param pszName The property name.
1260 * @param ppszValue Where to return the property value. Free this
1261 * using RTStrFree. Optional.
1262 */
1263int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
1264{
1265 int rc;
1266
1267 /*
1268 * Look it up in the scheduled changes.
1269 */
1270 size_t i = pState->cSvnPropChanges;
1271 while (i-- > 0)
1272 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1273 {
1274 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1275 if (!pszValue)
1276 return VERR_NOT_FOUND;
1277 if (ppszValue)
1278 return RTStrDupEx(ppszValue, pszValue);
1279 return VINF_SUCCESS;
1280 }
1281
1282#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1283 if (g_fSvnFunctionPointersValid)
1284 {
1285 rc = scmSvnQueryPropertyUsingApi(pState->pszFilename, pszName, ppszValue);
1286 if (rc != VERR_NOT_SUPPORTED)
1287 return rc;
1288 /* Fallback: */
1289 }
1290#endif
1291
1292 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1293 {
1294 /*
1295 * Hack: Read the .svn/props/<file>.svn-work file exists.
1296 */
1297 char szPath[RTPATH_MAX];
1298 rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
1299 if (RT_SUCCESS(rc) && !RTFileExists(szPath))
1300 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
1301 if (RT_SUCCESS(rc))
1302 {
1303 SCMSTREAM Stream;
1304 rc = ScmStreamInitForReading(&Stream, szPath);
1305 if (RT_SUCCESS(rc))
1306 {
1307 /*
1308 * The current format is K len\n<name>\nV len\n<value>\n" ... END.
1309 */
1310 rc = VERR_NOT_FOUND;
1311 size_t const cchName = strlen(pszName);
1312 SCMEOL enmEol;
1313 size_t cchLine;
1314 const char *pchLine;
1315 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1316 {
1317 /*
1318 * Parse the 'K num' / 'END' line.
1319 */
1320 if ( cchLine == 3
1321 && !memcmp(pchLine, "END", 3))
1322 break;
1323 size_t cchKey;
1324 if ( cchLine < 3
1325 || pchLine[0] != 'K'
1326 || pchLine[1] != ' '
1327 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
1328 || cchKey == 0
1329 || cchKey > 4096)
1330 {
1331 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1332 rc = VERR_PARSE_ERROR;
1333 break;
1334 }
1335
1336 /*
1337 * Match the key and skip to the value line. Don't bother with
1338 * names containing EOL markers.
1339 */
1340 size_t const offKey = ScmStreamTell(&Stream);
1341 bool fMatch = cchName == cchKey;
1342 if (fMatch)
1343 {
1344 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1345 if (!pchLine)
1346 break;
1347 fMatch = cchLine == cchName
1348 && !memcmp(pchLine, pszName, cchName);
1349 }
1350
1351 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
1352 break;
1353 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1354 break;
1355
1356 /*
1357 * Read and Parse the 'V num' line.
1358 */
1359 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1360 if (!pchLine)
1361 break;
1362 size_t cchValue;
1363 if ( cchLine < 3
1364 || pchLine[0] != 'V'
1365 || pchLine[1] != ' '
1366 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
1367 || cchValue > _1M)
1368 {
1369 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1370 rc = VERR_PARSE_ERROR;
1371 break;
1372 }
1373
1374 /*
1375 * If we have a match, allocate a return buffer and read the
1376 * value into it. Otherwise skip this value and continue
1377 * searching.
1378 */
1379 if (fMatch)
1380 {
1381 if (!ppszValue)
1382 rc = VINF_SUCCESS;
1383 else
1384 {
1385 char *pszValue;
1386 rc = RTStrAllocEx(&pszValue, cchValue + 1);
1387 if (RT_SUCCESS(rc))
1388 {
1389 rc = ScmStreamRead(&Stream, pszValue, cchValue);
1390 if (RT_SUCCESS(rc))
1391 *ppszValue = pszValue;
1392 else
1393 RTStrFree(pszValue);
1394 }
1395 }
1396 break;
1397 }
1398
1399 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
1400 break;
1401 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1402 break;
1403 }
1404
1405 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
1406 {
1407 rc = ScmStreamGetStatus(&Stream);
1408 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
1409 }
1410 ScmStreamDelete(&Stream);
1411 }
1412 }
1413
1414 if (rc == VERR_FILE_NOT_FOUND)
1415 rc = VERR_NOT_FOUND;
1416 }
1417 else
1418 {
1419 const char *apszArgs[] = { g_szSvnPath, "propget", "--strict", pszName, pState->pszFilename, NULL };
1420 char *pszValue;
1421 rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszValue);
1422 if (RT_SUCCESS(rc))
1423 {
1424 if (pszValue && *pszValue)
1425 {
1426 if (ppszValue)
1427 {
1428 *ppszValue = pszValue;
1429 pszValue = NULL;
1430 }
1431 }
1432 else
1433 rc = VERR_NOT_FOUND;
1434 RTStrFree(pszValue);
1435 }
1436 }
1437 return rc;
1438}
1439
1440
1441/**
1442 * Queries the value of an SVN property on the parent dir/whatever.
1443 *
1444 * This will not adjust for scheduled changes to the parent!
1445 *
1446 * @returns IPRT status code.
1447 * @retval VERR_INVALID_STATE if not a SVN WC file.
1448 * @retval VERR_NOT_FOUND if the property wasn't found.
1449 * @param pState The rewrite state to work on.
1450 * @param pszName The property name.
1451 * @param ppszValue Where to return the property value. Free this
1452 * using RTStrFree. Optional.
1453 */
1454int ScmSvnQueryParentProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
1455{
1456 /*
1457 * Strip the filename and use ScmSvnQueryProperty.
1458 */
1459 char szPath[RTPATH_MAX];
1460 int rc = RTStrCopy(szPath, sizeof(szPath), pState->pszFilename);
1461 if (RT_SUCCESS(rc))
1462 {
1463 RTPathStripFilename(szPath);
1464 SCMRWSTATE ParentState;
1465 ParentState.pszFilename = szPath;
1466 ParentState.fFirst = false;
1467 ParentState.fIsInSvnWorkingCopy = true;
1468 ParentState.cSvnPropChanges = 0;
1469 ParentState.paSvnPropChanges = NULL;
1470 ParentState.rc = VINF_SUCCESS;
1471 rc = ScmSvnQueryProperty(&ParentState, pszName, ppszValue);
1472 if (RT_SUCCESS(rc))
1473 rc = ParentState.rc;
1474 }
1475 return rc;
1476}
1477
1478
1479/**
1480 * Schedules the setting of a property.
1481 *
1482 * @returns IPRT status code.
1483 * @retval VERR_INVALID_STATE if not a SVN WC file.
1484 * @param pState The rewrite state to work on.
1485 * @param pszName The name of the property to set.
1486 * @param pszValue The value. NULL means deleting it.
1487 */
1488int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
1489{
1490 /*
1491 * Update any existing entry first.
1492 */
1493 size_t i = pState->cSvnPropChanges;
1494 while (i-- > 0)
1495 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1496 {
1497 if (!pszValue)
1498 {
1499 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1500 pState->paSvnPropChanges[i].pszValue = NULL;
1501 }
1502 else
1503 {
1504 char *pszCopy;
1505 int rc = RTStrDupEx(&pszCopy, pszValue);
1506 if (RT_FAILURE(rc))
1507 return rc;
1508 pState->paSvnPropChanges[i].pszValue = pszCopy;
1509 }
1510 return VINF_SUCCESS;
1511 }
1512
1513 /*
1514 * Insert a new entry.
1515 */
1516 i = pState->cSvnPropChanges;
1517 if ((i % 32) == 0)
1518 {
1519 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
1520 if (!pvNew)
1521 return VERR_NO_MEMORY;
1522 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
1523 }
1524
1525 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
1526 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
1527 if ( pState->paSvnPropChanges[i].pszName
1528 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
1529 pState->cSvnPropChanges = i + 1;
1530 else
1531 {
1532 RTStrFree(pState->paSvnPropChanges[i].pszName);
1533 pState->paSvnPropChanges[i].pszName = NULL;
1534 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1535 pState->paSvnPropChanges[i].pszValue = NULL;
1536 return VERR_NO_MEMORY;
1537 }
1538 return VINF_SUCCESS;
1539}
1540
1541
1542/**
1543 * Schedules a property deletion.
1544 *
1545 * @returns IPRT status code.
1546 * @param pState The rewrite state to work on.
1547 * @param pszName The name of the property to delete.
1548 */
1549int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
1550{
1551 return ScmSvnSetProperty(pState, pszName, NULL);
1552}
1553
1554
1555/**
1556 * Applies any SVN property changes to the work copy of the file.
1557 *
1558 * @returns IPRT status code.
1559 * @param pState The rewrite state which SVN property changes
1560 * should be applied.
1561 */
1562int ScmSvnDisplayChanges(PSCMRWSTATE pState)
1563{
1564 size_t i = pState->cSvnPropChanges;
1565 while (i-- > 0)
1566 {
1567 const char *pszName = pState->paSvnPropChanges[i].pszName;
1568 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1569 if (pszValue)
1570 ScmVerbose(pState, 0, "svn propset '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
1571 else
1572 ScmVerbose(pState, 0, "svn propdel '%s' %s\n", pszName, pState->pszFilename);
1573 }
1574
1575 return VINF_SUCCESS;
1576}
1577
1578/**
1579 * Applies any SVN property changes to the work copy of the file.
1580 *
1581 * @returns IPRT status code.
1582 * @param pState The rewrite state which SVN property changes
1583 * should be applied.
1584 */
1585int ScmSvnApplyChanges(PSCMRWSTATE pState)
1586{
1587#ifdef SCM_WITH_LATER
1588 if (0)
1589 {
1590 return ...;
1591 }
1592
1593 /* Fallback: */
1594#endif
1595
1596 /*
1597 * Iterate thru the changes and apply them by starting the svn client.
1598 */
1599 for (size_t i = 0; i < pState->cSvnPropChanges; i++)
1600 {
1601 const char *apszArgv[6];
1602 apszArgv[0] = g_szSvnPath;
1603 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "propset" : "propdel";
1604 apszArgv[2] = pState->paSvnPropChanges[i].pszName;
1605 int iArg = 3;
1606 if (pState->paSvnPropChanges[i].pszValue)
1607 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
1608 apszArgv[iArg++] = pState->pszFilename;
1609 apszArgv[iArg++] = NULL;
1610
1611 int rc = scmSvnRun(pState, apszArgv, false);
1612 if (RT_FAILURE(rc))
1613 return rc;
1614 }
1615
1616 return VINF_SUCCESS;
1617}
1618
1619
1620/**
1621 * Initializes the subversion interface.
1622 */
1623void ScmSvnInit(void)
1624{
1625 scmSvnFindSvnBinary(NULL);
1626}
1627
1628
1629void ScmSvnTerm(void)
1630{
1631#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1632 scmSvnFlushClientContextAndPool();
1633#endif
1634}
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