VirtualBox

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

Last change on this file since 91933 was 86066, checked in by vboxsync, 4 years ago

scm: New svn and apr search locations for solaris and a hack for dealing with libapr*.so being installed in a different location from the svn bits.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 58.4 KB
Line 
1/* $Id: scmsubversion.cpp 86066 2020-09-08 14:16:53Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager, Subversion Access.
4 */
5
6/*
7 * Copyright (C) 2010-2020 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 NULL /*pvExtraData*/,
422 &hProc);
423 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
424 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
425
426 if (RT_SUCCESS(rc))
427 {
428 /*
429 * Process output and wait for the process to finish.
430 */
431 size_t cbStdOut = 0;
432 size_t offStdOut = 0;
433 size_t cbStdErr = 0;
434 size_t offStdErr = 0;
435 for (;;)
436 {
437 if (hPipeStdOutR != NIL_RTPIPE)
438 rc = rtProcProcessOutput(rc, &hPipeStdOutR, &cbStdOut, &offStdOut, ppszStdOut, hPollSet, 1);
439 if (hPipeStdErrR != NIL_RTPIPE)
440 rc = rtProcProcessOutput(rc, &hPipeStdErrR, &cbStdErr, &offStdErr, ppszStdErr, hPollSet, 2);
441 if (hPipeStdOutR == NIL_RTPIPE && hPipeStdErrR == NIL_RTPIPE)
442 break;
443
444 if (hProc != NIL_RTPROCESS)
445 {
446 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_NOBLOCK, pStatus);
447 if (rc2 != VERR_PROCESS_RUNNING)
448 {
449 if (RT_FAILURE(rc2))
450 rc = rc2;
451 hProc = NIL_RTPROCESS;
452 }
453 }
454
455 rc2 = RTPoll(hPollSet, 10000, NULL, NULL);
456 Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT);
457 }
458
459 if (RT_SUCCESS(rc))
460 {
461 if ( (ppszStdOut && *ppszStdOut && !RTStrIsValidEncoding(*ppszStdOut))
462 || (ppszStdErr && *ppszStdErr && !RTStrIsValidEncoding(*ppszStdErr)) )
463 rc = VERR_NO_TRANSLATION;
464 }
465
466 /*
467 * No more output, just wait for it to finish.
468 */
469 if (hProc != NIL_RTPROCESS)
470 {
471 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
472 if (RT_FAILURE(rc2))
473 rc = rc2;
474 }
475 }
476 RTPollSetDestroy(hPollSet);
477 }
478 }
479
480 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
481 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
482 rc2 = RTHandleClose(&hChildStdIn); AssertRC(rc2);
483 rc2 = RTPipeClose(hPipeStdErrR); AssertRC(rc2);
484 rc2 = RTPipeClose(hPipeStdOutR); AssertRC(rc2);
485 return rc;
486}
487
488
489/**
490 * Runs a process, waiting for it to complete.
491 *
492 * @returns IPRT status code
493 *
494 * @param pszExec Executable image to use to create the child process.
495 * @param papszArgs Pointer to an array of arguments to the child. The
496 * array terminated by an entry containing NULL.
497 * @param hEnv Handle to the environment block for the child.
498 * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX.
499 * @param pStatus Where to return the status on success.
500 */
501int RTProcExec(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
502 PRTPROCSTATUS pStatus)
503{
504 int rc;
505
506 /*
507 * Clear output argument (no returning failure here, simply crash!).
508 */
509 AssertPtr(pStatus);
510 pStatus->enmReason = RTPROCEXITREASON_ABEND;
511 pStatus->iStatus = RTEXITCODE_FAILURE;
512
513 /*
514 * Check input arguments.
515 */
516 AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
517
518 /*
519 * Set up /dev/null redirections.
520 */
521 PRTHANDLE aph[3] = { NULL, NULL, NULL };
522 RTHANDLE ah[3];
523 for (uint32_t i = 0; i < 3; i++)
524 {
525 ah[i].enmType = RTHANDLETYPE_FILE;
526 ah[i].u.hFile = NIL_RTFILE;
527 }
528 rc = VINF_SUCCESS;
529 if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
530 {
531 aph[0] = &ah[0];
532 rc = RTFileOpenBitBucket(&ah[0].u.hFile, RTFILE_O_READ);
533 }
534 if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
535 {
536 aph[1] = &ah[1];
537 rc = RTFileOpenBitBucket(&ah[1].u.hFile, RTFILE_O_WRITE);
538 }
539 if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
540 {
541 aph[2] = &ah[2];
542 rc = RTFileOpenBitBucket(&ah[2].u.hFile, RTFILE_O_WRITE);
543 }
544
545 /*
546 * Create the process.
547 */
548 RTPROCESS hProc = NIL_RTPROCESS;
549 if (RT_SUCCESS(rc))
550 rc = RTProcCreateEx(pszExec,
551 papszArgs,
552 hEnv,
553 0 /*fFlags*/,
554 aph[0],
555 aph[1],
556 aph[2],
557 NULL /*pszAsUser*/,
558 NULL /*pszPassword*/,
559 NULL /*pvExtraData*/,
560 &hProc);
561
562 for (uint32_t i = 0; i < 3; i++)
563 RTFileClose(ah[i].u.hFile);
564
565 if (RT_SUCCESS(rc))
566 rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
567 return rc;
568}
569
570
571
572/**
573 * Executes SVN and gets the output.
574 *
575 * Standard error is suppressed.
576 *
577 * @returns VINF_SUCCESS if the command executed successfully.
578 * @param pState The rewrite state to work on. Can be NULL.
579 * @param papszArgs The SVN argument.
580 * @param fNormalFailureOk Whether normal failure is ok.
581 * @param ppszStdOut Where to return the output on success.
582 */
583static int scmSvnRunAndGetOutput(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk, char **ppszStdOut)
584{
585 *ppszStdOut = NULL;
586
587#ifdef SCM_WITH_DYNAMIC_LIB_SVN
588 scmSvnFlushClientContextAndPool();
589#endif
590
591 char *pszCmdLine = NULL;
592 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
593 if (RT_FAILURE(rc))
594 return rc;
595 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
596
597 RTPROCSTATUS Status;
598 rc = RTProcExecToString(g_szSvnPath, papszArgs, RTENV_DEFAULT,
599 RTPROCEXEC_FLAGS_STD_NULL, &Status, ppszStdOut, NULL);
600
601 if ( RT_SUCCESS(rc)
602 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
603 || Status.iStatus != 0) )
604 {
605 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
606 RTMsgError("%s: %s -> %s %u\n",
607 pState ? pState->pszFilename : "<NONE>", pszCmdLine,
608 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
609 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
610 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
611 : "abducted by alien",
612 Status.iStatus);
613 rc = VERR_GENERAL_FAILURE;
614 }
615 else if (RT_FAILURE(rc))
616 {
617 if (pState)
618 RTMsgError("%s: executing: %s => %Rrc\n", pState->pszFilename, pszCmdLine, rc);
619 else
620 RTMsgError("executing: %s => %Rrc\n", pszCmdLine, rc);
621 }
622
623 if (RT_FAILURE(rc))
624 {
625 RTStrFree(*ppszStdOut);
626 *ppszStdOut = NULL;
627 }
628 RTStrFree(pszCmdLine);
629 return rc;
630}
631
632
633/**
634 * Executes SVN.
635 *
636 * Standard error and standard output is suppressed.
637 *
638 * @returns VINF_SUCCESS if the command executed successfully.
639 * @param pState The rewrite state to work on.
640 * @param papszArgs The SVN argument.
641 * @param fNormalFailureOk Whether normal failure is ok.
642 */
643static int scmSvnRun(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk)
644{
645#ifdef SCM_WITH_DYNAMIC_LIB_SVN
646 scmSvnFlushClientContextAndPool();
647#endif
648
649 char *pszCmdLine = NULL;
650 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
651 if (RT_FAILURE(rc))
652 return rc;
653 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
654
655 /* Lazy bird uses RTProcExec. */
656 RTPROCSTATUS Status;
657 rc = RTProcExec(g_szSvnPath, papszArgs, RTENV_DEFAULT, RTPROCEXEC_FLAGS_STD_NULL, &Status);
658
659 if ( RT_SUCCESS(rc)
660 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
661 || Status.iStatus != 0) )
662 {
663 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
664 RTMsgError("%s: %s -> %s %u\n",
665 pState->pszFilename,
666 pszCmdLine,
667 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
668 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
669 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
670 : "abducted by alien",
671 Status.iStatus);
672 rc = VERR_GENERAL_FAILURE;
673 }
674 else if (RT_FAILURE(rc))
675 RTMsgError("%s: %s -> %Rrc\n", pState->pszFilename, pszCmdLine, rc);
676
677 RTStrFree(pszCmdLine);
678 return rc;
679}
680
681
682#ifdef SCM_WITH_DYNAMIC_LIB_SVN
683/**
684 * Attempts to resolve the necessary subversion and apache portable runtime APIs
685 * we require dynamically.
686 *
687 * Will set all global function pointers and g_fSvnFunctionPointersValid to true
688 * on success.
689 */
690static void scmSvnTryResolveFunctions(void)
691{
692 char szPath[RTPATH_MAX];
693 int rc = RTStrCopy(szPath, sizeof(szPath), g_szSvnPath);
694 if (RT_SUCCESS(rc))
695 {
696 RTPathStripFilename(szPath);
697 char *pszEndPath = strchr(szPath, '\0');
698# ifdef RT_OS_WINDOWS
699 RTPathChangeToDosSlashes(szPath, false);
700# endif
701
702 /*
703 * Try various prefixes/suffxies/locations.
704 */
705 static struct
706 {
707 const char *pszPrefix;
708 const char *pszSuffix;
709 } const s_aVariations[] =
710 {
711# ifdef RT_OS_WINDOWS
712 { "SlikSvn-lib", "-1.dll" }, /* SlikSVN */
713 { "lib", "-1.dll" }, /* Win32Svn,CollabNet,++ */
714# elif defined(RT_OS_DARWIN)
715 { "../lib/lib", "-1.dylib" },
716# else
717 { "../lib/lib", ".so" },
718 { "../lib/lib", "-1.so" },
719# if ARCH_BITS == 32
720 { "../lib32/lib", ".so" },
721 { "../lib32/lib", "-1.so" },
722# else
723 { "../lib64/lib", ".so" },
724 { "../lib64/lib", "-1.so" },
725# ifdef RT_OS_SOLARIS
726 { "../lib/svn/amd64/lib", ".so" },
727 { "../lib/svn/amd64/lib", "-1.so" },
728 { "../apr/1.6/lib/amd64/lib", ".so" },
729 { "../apr/1.6/lib/amd64/lib", "-1.so" },
730# endif
731# endif
732# ifdef RT_ARCH_X86
733 { "../lib/i386-linux-gnu/lib", ".so" },
734 { "../lib/i386-linux-gnu/lib", "-1.so" },
735# elif defined(RT_ARCH_AMD64)
736 { "../lib/x86_64-linux-gnu/lib", ".so" },
737 { "../lib/x86_64-linux-gnu/lib", "-1.so" },
738# endif
739# endif
740 };
741 for (unsigned iVar = 0; iVar < RT_ELEMENTS(s_aVariations); iVar++)
742 {
743 /*
744 * Try load the svn_client library ...
745 */
746 static const char * const s_apszLibraries[] = { "svn_client", "svn_subr", "apr" };
747 RTLDRMOD ahMods[RT_ELEMENTS(s_apszLibraries)] = { NIL_RTLDRMOD, NIL_RTLDRMOD, NIL_RTLDRMOD };
748
749 rc = VINF_SUCCESS;
750 unsigned iLib;
751 for (iLib = 0; iLib < RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc); iLib++)
752 {
753 static const char * const s_apszSuffixes[] = { "", ".0", ".1" };
754 for (unsigned iSuff = 0; iSuff < RT_ELEMENTS(s_apszSuffixes); iSuff++)
755 {
756 *pszEndPath = '\0';
757 rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar].pszPrefix);
758 if (RT_SUCCESS(rc))
759 rc = RTStrCat(szPath, sizeof(szPath), s_apszLibraries[iLib]);
760 if (RT_SUCCESS(rc))
761 rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar].pszSuffix);
762 if (RT_SUCCESS(rc))
763 rc = RTStrCat(szPath, sizeof(szPath), s_apszSuffixes[iSuff]);
764 if (RT_SUCCESS(rc))
765 {
766# ifdef RT_OS_WINDOWS
767 RTPathChangeToDosSlashes(pszEndPath, false);
768# endif
769 rc = RTLdrLoadEx(szPath, &ahMods[iLib], RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR , NULL);
770 if (RT_SUCCESS(rc))
771 {
772 RTMEM_WILL_LEAK(ahMods[iLib]);
773 break;
774 }
775 }
776 }
777# ifdef RT_OS_SOLARIS
778 /*
779 * HACK: Solaris may keep libapr.so separately from svn, so do a separate search for it.
780 */
781 /** @todo It would make a lot more sense to use the dlfcn.h machinery to figure
782 * out which libapr*.so* file was loaded into the process together with
783 * the two svn libraries and get a dlopen handle for it. We risk ending
784 * up with the completely wrong libapr here! */
785 if (iLib == RT_ELEMENTS(s_apszLibraries) - 1 && RT_FAILURE(rc))
786 {
787 ahMods[iLib] = NIL_RTLDRMOD;
788 for (unsigned iVar2 = 0; iVar2 < RT_ELEMENTS(s_aVariations) && ahMods[iLib] == NIL_RTLDRMOD; iVar2++)
789 for (unsigned iSuff2 = 0; iSuff2 < RT_ELEMENTS(s_apszSuffixes) && ahMods[iLib] == NIL_RTLDRMOD; iSuff2++)
790 {
791 *pszEndPath = '\0';
792 rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar2].pszPrefix);
793 if (RT_SUCCESS(rc))
794 rc = RTStrCat(szPath, sizeof(szPath), s_apszLibraries[iLib]);
795 if (RT_SUCCESS(rc))
796 rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar2].pszSuffix);
797 if (RT_SUCCESS(rc))
798 rc = RTStrCat(szPath, sizeof(szPath), s_apszSuffixes[iSuff2]);
799 if (RT_SUCCESS(rc))
800 rc = RTLdrLoadEx(szPath, &ahMods[iLib], RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR, NULL);
801 if (RT_SUCCESS(rc))
802 RTMEM_WILL_LEAK(ahMods[iLib]);
803 else
804 ahMods[iLib] = NIL_RTLDRMOD;
805 }
806 }
807# endif /* RT_OS_SOLARIS */
808 }
809 if (iLib == RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc))
810 {
811 static const struct
812 {
813 unsigned iLib;
814 const char *pszSymbol;
815 uintptr_t *ppfn; /**< The nothrow attrib of PFNRT goes down the wrong way with Clang 11, thus uintptr_t. */
816 } s_aSymbols[] =
817 {
818 { 2, "apr_initialize", (uintptr_t *)&g_pfnAprInitialize },
819 { 2, "apr_hash_first", (uintptr_t *)&g_pfnAprHashFirst },
820 { 2, "apr_hash_next", (uintptr_t *)&g_pfnAprHashNext },
821 { 2, "apr_hash_this_val", (uintptr_t *)&g_pfnAprHashThisVal },
822 { 1, "svn_pool_create_ex", (uintptr_t *)&g_pfnSvnPoolCreateEx },
823 { 2, "apr_pool_clear", (uintptr_t *)&g_pfnAprPoolClear },
824 { 2, "apr_pool_destroy", (uintptr_t *)&g_pfnAprPoolDestroy },
825 { 0, "svn_client_create_context", (uintptr_t *)&g_pfnSvnClientCreateContext },
826 { 0, "svn_client_propget4", (uintptr_t *)&g_pfnSvnClientPropGet4 },
827 };
828 for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++)
829 {
830 rc = RTLdrGetSymbol(ahMods[s_aSymbols[i].iLib], s_aSymbols[i].pszSymbol,
831 (void **)(uintptr_t)s_aSymbols[i].ppfn);
832 if (RT_FAILURE(rc))
833 {
834 ScmVerbose(NULL, 0, "Failed to resolve '%s' in '%s'",
835 s_aSymbols[i].pszSymbol, s_apszLibraries[s_aSymbols[i].iLib]);
836 break;
837 }
838 }
839 if (RT_SUCCESS(rc))
840 {
841 apr_status_t rcApr = g_pfnAprInitialize();
842 if (rcApr == 0)
843 {
844 ScmVerbose(NULL, 1, "Found subversion APIs.\n");
845 g_fSvnFunctionPointersValid = true;
846 }
847 else
848 {
849 ScmVerbose(NULL, 0, "apr_initialize failed: %#x (%d)\n", rcApr, rcApr);
850 AssertMsgFailed(("%#x (%d)\n", rc, rc));
851 }
852 return;
853 }
854 }
855
856 while (iLib-- > 0)
857 RTLdrClose(ahMods[iLib]);
858 }
859 }
860}
861#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
862
863
864/**
865 * Finds the svn binary, updating g_szSvnPath and g_enmSvnVersion.
866 */
867static void scmSvnFindSvnBinary(PSCMRWSTATE pState)
868{
869 /* Already been called? */
870 if (g_szSvnPath[0] != '\0')
871 return;
872
873 /*
874 * Locate it.
875 */
876 /** @todo code page fun... */
877#ifdef RT_OS_WINDOWS
878 const char *pszEnvVar = RTEnvGet("Path");
879#else
880 const char *pszEnvVar = RTEnvGet("PATH");
881#endif
882 if (pszEnvVar)
883 {
884#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
885 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
886#else
887 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
888#endif
889 if (RT_FAILURE(rc))
890 strcpy(g_szSvnPath, "svn");
891 }
892 else
893 strcpy(g_szSvnPath, "svn");
894
895 /*
896 * Check the version.
897 */
898 const char *apszArgs[] = { g_szSvnPath, "--version", "--quiet", NULL };
899 char *pszVersion;
900 int rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszVersion);
901 if (RT_SUCCESS(rc))
902 {
903 char *pszStripped = RTStrStrip(pszVersion);
904 if (RTStrVersionCompare(pszStripped, "1.8") >= 0)
905 g_enmSvnVersion = kScmSvnVersion_1_8;
906 else if (RTStrVersionCompare(pszStripped, "1.7") >= 0)
907 g_enmSvnVersion = kScmSvnVersion_1_7;
908 else if (RTStrVersionCompare(pszStripped, "1.6") >= 0)
909 g_enmSvnVersion = kScmSvnVersion_1_6;
910 else
911 g_enmSvnVersion = kScmSvnVersion_Ancient;
912 RTStrFree(pszVersion);
913 }
914 else
915 g_enmSvnVersion = kScmSvnVersion_Ancient;
916
917#ifdef SCM_WITH_DYNAMIC_LIB_SVN
918 /*
919 * If we got version 1.8 or later, try see if we can locate a few of the
920 * simpler SVN APIs.
921 */
922 g_fSvnFunctionPointersValid = false;
923 if (g_enmSvnVersion >= kScmSvnVersion_1_8)
924 scmSvnTryResolveFunctions();
925#endif
926}
927
928
929/**
930 * Construct a dot svn filename for the file being rewritten.
931 *
932 * @returns IPRT status code.
933 * @param pState The rewrite state (for the name).
934 * @param pszDir The directory, including ".svn/".
935 * @param pszSuff The filename suffix.
936 * @param pszDst The output buffer. RTPATH_MAX in size.
937 */
938static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
939{
940 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
941 RTPathStripFilename(pszDst);
942
943 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
944 if (RT_SUCCESS(rc))
945 {
946 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
947 if (RT_SUCCESS(rc))
948 {
949 size_t cchDst = strlen(pszDst);
950 size_t cchSuff = strlen(pszSuff);
951 if (cchDst + cchSuff < RTPATH_MAX)
952 {
953 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
954 return VINF_SUCCESS;
955 }
956 else
957 rc = VERR_BUFFER_OVERFLOW;
958 }
959 }
960 return rc;
961}
962
963/**
964 * Interprets the specified string as decimal numbers.
965 *
966 * @returns true if parsed successfully, false if not.
967 * @param pch The string (not terminated).
968 * @param cch The string length.
969 * @param pu Where to return the value.
970 */
971static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
972{
973 size_t u = 0;
974 while (cch-- > 0)
975 {
976 char ch = *pch++;
977 if (ch < '0' || ch > '9')
978 return false;
979 u *= 10;
980 u += ch - '0';
981 }
982 *pu = u;
983 return true;
984}
985
986
987#ifdef SCM_WITH_DYNAMIC_LIB_SVN
988
989/**
990 * Wrapper around RTPathAbs.
991 * @returns Same as RTPathAbs.
992 * @param pszPath The relative path.
993 * @param pszAbsPath Where to return the absolute path.
994 * @param cbAbsPath Size of the @a pszAbsPath buffer.
995 */
996static int scmSvnAbsPath(const char *pszPath, char *pszAbsPath, size_t cbAbsPath)
997{
998 int rc = RTPathAbs(pszPath, pszAbsPath, cbAbsPath);
999# if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
1000 if (RT_SUCCESS(rc))
1001 {
1002 RTPathChangeToUnixSlashes(pszAbsPath, true /*fForce*/);
1003 /* To avoid: svn: E235000: In file '..\..\..\subversion\libsvn_client\prop_commands.c' line 796: assertion failed (svn_dirent_is_absolute(target)) */
1004 if (pszAbsPath[1] == ':')
1005 pszAbsPath[0] = RT_C_TO_UPPER(pszAbsPath[0]);
1006 }
1007# endif
1008 return rc;
1009}
1010
1011
1012/**
1013 * Gets a client context and pool.
1014 *
1015 * This implements caching.
1016 *
1017 * @returns IPRT status code.
1018 * @param ppCtx Where to return the context
1019 * @param ppPool Where to return the pool.
1020 */
1021static int scmSvnGetClientContextAndPool(svn_client_ctx_t **ppCtx, apr_pool_t **ppPool)
1022{
1023 /*
1024 * Use cached if present.
1025 */
1026 if (g_pSvnClientCtx && g_pSvnPool)
1027 {
1028 g_cSvnClientCtxUsed++;
1029 *ppCtx = g_pSvnClientCtx;
1030 *ppPool = g_pSvnPool;
1031 return VINF_SUCCESS;
1032 }
1033 Assert(!g_pSvnClientCtx);
1034 Assert(!g_pSvnPool);
1035
1036 /*
1037 * Create new pool and context.
1038 */
1039 apr_pool_t *pPool = g_pfnSvnPoolCreateEx(NULL, NULL);
1040 if (pPool)
1041 {
1042 svn_client_ctx_t *pCtx = NULL;
1043 svn_error_t *pErr = g_pfnSvnClientCreateContext(&pCtx, pPool);
1044 if (!pErr)
1045 {
1046 g_cSvnClientCtxUsed = 1;
1047 g_pSvnClientCtx = *ppCtx = pCtx;
1048 g_pSvnPool = *ppPool = pPool;
1049 return VINF_SUCCESS;
1050 }
1051 g_pfnAprPoolDestroy(pPool);
1052 }
1053
1054 *ppCtx = NULL;
1055 *ppPool = NULL;
1056 return VERR_GENERAL_FAILURE;
1057}
1058
1059
1060/**
1061 * Puts back a client context and pool after use.
1062 *
1063 * @param pCtx The context.
1064 * @param pPool The pool.
1065 * @param fFlush Whether to flush it.
1066 */
1067static void scmSvnPutClientContextAndPool(svn_client_ctx_t *pCtx, apr_pool_t *pPool, bool fFlush)
1068{
1069 if (fFlush || g_cSvnClientCtxUsed > 4096) /* Disable this to force new context every time. */
1070 {
1071 g_pfnAprPoolDestroy(pPool);
1072 g_pSvnPool = NULL;
1073 g_pSvnClientCtx = NULL;
1074 }
1075 RT_NOREF(pCtx, fFlush);
1076}
1077
1078
1079/**
1080 * Flushes the cached client context and pool
1081 */
1082static void scmSvnFlushClientContextAndPool(void)
1083{
1084 if (g_pSvnPool)
1085 scmSvnPutClientContextAndPool(g_pSvnClientCtx, g_pSvnPool, true /*fFlush*/);
1086 Assert(!g_pSvnPool);
1087}
1088
1089
1090/**
1091 * Checks if @a pszPath exists in the current WC.
1092 *
1093 * @returns true, false or -1. In the latter case, please use the fallback.
1094 * @param pszPath Path to the object that should be investigated.
1095 */
1096static int scmSvnIsObjectInWorkingCopy(const char *pszPath)
1097{
1098 /* svn_client_propget4 and later requires absolute target path. */
1099 char szAbsPath[RTPATH_MAX];
1100 int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
1101 if (RT_SUCCESS(rc))
1102 {
1103 apr_pool_t *pPool;
1104 svn_client_ctx_t *pCtx = NULL;
1105 rc = scmSvnGetClientContextAndPool(&pCtx, &pPool);
1106 if (RT_SUCCESS(rc))
1107 {
1108 /* Make the call. */
1109 apr_hash_t *pHash = NULL;
1110 svn_opt_revision_t Rev;
1111 RT_ZERO(Rev);
1112 Rev.kind = svn_opt_revision_working;
1113 Rev.value.number = -1L;
1114 svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, "svn:no-such-property", szAbsPath, &Rev, &Rev,
1115 NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/,
1116 pCtx, pPool, pPool);
1117 if (!pErr)
1118 rc = true;
1119 else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
1120 rc = false;
1121
1122 scmSvnPutClientContextAndPool(pCtx, pPool, false);
1123 }
1124 }
1125 return rc;
1126}
1127
1128#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
1129
1130
1131/**
1132 * Checks if the file we're operating on is part of a SVN working copy.
1133 *
1134 * @returns true if it is, false if it isn't or we cannot tell.
1135 * @param pState The rewrite state to work on. Will use the
1136 * fIsInSvnWorkingCopy member for caching the result.
1137 */
1138bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
1139{
1140 /*
1141 * We don't ask SVN twice as that's expensive.
1142 */
1143 if (pState->fIsInSvnWorkingCopy != 0)
1144 return pState->fIsInSvnWorkingCopy > 0;
1145
1146#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1147 if (g_fSvnFunctionPointersValid)
1148 {
1149 int rc = scmSvnIsObjectInWorkingCopy(pState->pszFilename);
1150 if (rc == (int)true || rc == (int)false)
1151 {
1152 pState->fIsInSvnWorkingCopy = rc == (int)true ? 1 : -1;
1153 return rc == (int)true;
1154 }
1155 }
1156
1157 /* Fallback: */
1158#endif
1159 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1160 {
1161 /*
1162 * Hack: check if the .svn/text-base/<file>.svn-base file exists.
1163 */
1164 char szPath[RTPATH_MAX];
1165 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
1166 if (RT_SUCCESS(rc))
1167 {
1168 if (RTFileExists(szPath))
1169 {
1170 pState->fIsInSvnWorkingCopy = 1;
1171 return true;
1172 }
1173 }
1174 }
1175 else
1176 {
1177 const char *apszArgs[] = { g_szSvnPath, "proplist", pState->pszFilename, NULL };
1178 char *pszValue;
1179 int rc = scmSvnRunAndGetOutput(pState, apszArgs, true, &pszValue);
1180 if (RT_SUCCESS(rc))
1181 {
1182 RTStrFree(pszValue);
1183 pState->fIsInSvnWorkingCopy = 1;
1184 return true;
1185 }
1186 }
1187 pState->fIsInSvnWorkingCopy = -1;
1188 return false;
1189}
1190
1191
1192/**
1193 * Checks if the specified directory is part of a SVN working copy.
1194 *
1195 * @returns true if it is, false if it isn't or we cannot tell.
1196 * @param pszDir The directory in question.
1197 */
1198bool ScmSvnIsDirInWorkingCopy(const char *pszDir)
1199{
1200#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1201 if (g_fSvnFunctionPointersValid)
1202 {
1203 int rc = scmSvnIsObjectInWorkingCopy(pszDir);
1204 if (rc == (int)true || rc == (int)false)
1205 return rc == (int)true;
1206 }
1207
1208 /* Fallback: */
1209#endif
1210 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1211 {
1212 /*
1213 * Hack: check if the .svn/ dir exists.
1214 */
1215 char szPath[RTPATH_MAX];
1216 int rc = RTPathJoin(szPath, sizeof(szPath), pszDir, ".svn");
1217 if (RT_SUCCESS(rc))
1218 return RTDirExists(szPath);
1219 }
1220 else
1221 {
1222 const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pszDir, NULL };
1223 char *pszValue;
1224 int rc = scmSvnRunAndGetOutput(NULL, apszArgs, true, &pszValue);
1225 if (RT_SUCCESS(rc))
1226 {
1227 RTStrFree(pszValue);
1228 return true;
1229 }
1230 }
1231 return false;
1232}
1233
1234
1235#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1236/**
1237 * Checks if @a pszPath exists in the current WC.
1238 *
1239 * @returns IPRT status code - VERR_NOT_SUPPORT if fallback should be attempted.
1240 * @param pszPath Path to the object that should be investigated.
1241 * @param pszProperty The property name.
1242 * @param ppszValue Where to return the property value. Optional.
1243 */
1244static int scmSvnQueryPropertyUsingApi(const char *pszPath, const char *pszProperty, char **ppszValue)
1245{
1246 /* svn_client_propget4 and later requires absolute target path. */
1247 char szAbsPath[RTPATH_MAX];
1248 int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
1249 if (RT_SUCCESS(rc))
1250 {
1251 apr_pool_t *pPool;
1252 svn_client_ctx_t *pCtx = NULL;
1253 rc = scmSvnGetClientContextAndPool(&pCtx, &pPool);
1254 if (RT_SUCCESS(rc))
1255 {
1256 /* Make the call. */
1257 apr_hash_t *pHash = NULL;
1258 svn_opt_revision_t Rev;
1259 RT_ZERO(Rev);
1260 Rev.kind = svn_opt_revision_working;
1261 Rev.value.number = -1L;
1262 svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, pszProperty, szAbsPath, &Rev, &Rev,
1263 NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/,
1264 pCtx, pPool, pPool);
1265 if (!pErr)
1266 {
1267 /* Get the first value, if any. */
1268 rc = VERR_NOT_FOUND;
1269 apr_hash_index_t *pHashIdx = g_pfnAprHashFirst(pPool, pHash);
1270 if (pHashIdx)
1271 {
1272 const char **ppszFirst = (const char **)g_pfnAprHashThisVal(pHashIdx);
1273 if (ppszFirst && *ppszFirst)
1274 {
1275 if (ppszValue)
1276 rc = RTStrDupEx(ppszValue, *ppszFirst);
1277 else
1278 rc = VINF_SUCCESS;
1279 }
1280 }
1281 }
1282 else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
1283 rc = VERR_INVALID_STATE;
1284 else
1285 rc = VERR_GENERAL_FAILURE;
1286
1287 scmSvnPutClientContextAndPool(pCtx, pPool, false);
1288 }
1289 }
1290 return rc;
1291}
1292#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
1293
1294
1295/**
1296 * Queries the value of an SVN property.
1297 *
1298 * This will automatically adjust for scheduled changes.
1299 *
1300 * @returns IPRT status code.
1301 * @retval VERR_INVALID_STATE if not a SVN WC file.
1302 * @retval VERR_NOT_FOUND if the property wasn't found.
1303 * @param pState The rewrite state to work on.
1304 * @param pszName The property name.
1305 * @param ppszValue Where to return the property value. Free this
1306 * using RTStrFree. Optional.
1307 */
1308int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
1309{
1310 int rc;
1311
1312 /*
1313 * Look it up in the scheduled changes.
1314 */
1315 size_t i = pState->cSvnPropChanges;
1316 while (i-- > 0)
1317 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1318 {
1319 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1320 if (!pszValue)
1321 return VERR_NOT_FOUND;
1322 if (ppszValue)
1323 return RTStrDupEx(ppszValue, pszValue);
1324 return VINF_SUCCESS;
1325 }
1326
1327#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1328 if (g_fSvnFunctionPointersValid)
1329 {
1330 rc = scmSvnQueryPropertyUsingApi(pState->pszFilename, pszName, ppszValue);
1331 if (rc != VERR_NOT_SUPPORTED)
1332 return rc;
1333 /* Fallback: */
1334 }
1335#endif
1336
1337 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1338 {
1339 /*
1340 * Hack: Read the .svn/props/<file>.svn-work file exists.
1341 */
1342 char szPath[RTPATH_MAX];
1343 rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
1344 if (RT_SUCCESS(rc) && !RTFileExists(szPath))
1345 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
1346 if (RT_SUCCESS(rc))
1347 {
1348 SCMSTREAM Stream;
1349 rc = ScmStreamInitForReading(&Stream, szPath);
1350 if (RT_SUCCESS(rc))
1351 {
1352 /*
1353 * The current format is K len\n<name>\nV len\n<value>\n" ... END.
1354 */
1355 rc = VERR_NOT_FOUND;
1356 size_t const cchName = strlen(pszName);
1357 SCMEOL enmEol;
1358 size_t cchLine;
1359 const char *pchLine;
1360 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1361 {
1362 /*
1363 * Parse the 'K num' / 'END' line.
1364 */
1365 if ( cchLine == 3
1366 && !memcmp(pchLine, "END", 3))
1367 break;
1368 size_t cchKey;
1369 if ( cchLine < 3
1370 || pchLine[0] != 'K'
1371 || pchLine[1] != ' '
1372 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
1373 || cchKey == 0
1374 || cchKey > 4096)
1375 {
1376 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1377 rc = VERR_PARSE_ERROR;
1378 break;
1379 }
1380
1381 /*
1382 * Match the key and skip to the value line. Don't bother with
1383 * names containing EOL markers.
1384 */
1385 size_t const offKey = ScmStreamTell(&Stream);
1386 bool fMatch = cchName == cchKey;
1387 if (fMatch)
1388 {
1389 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1390 if (!pchLine)
1391 break;
1392 fMatch = cchLine == cchName
1393 && !memcmp(pchLine, pszName, cchName);
1394 }
1395
1396 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
1397 break;
1398 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1399 break;
1400
1401 /*
1402 * Read and Parse the 'V num' line.
1403 */
1404 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1405 if (!pchLine)
1406 break;
1407 size_t cchValue;
1408 if ( cchLine < 3
1409 || pchLine[0] != 'V'
1410 || pchLine[1] != ' '
1411 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
1412 || cchValue > _1M)
1413 {
1414 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1415 rc = VERR_PARSE_ERROR;
1416 break;
1417 }
1418
1419 /*
1420 * If we have a match, allocate a return buffer and read the
1421 * value into it. Otherwise skip this value and continue
1422 * searching.
1423 */
1424 if (fMatch)
1425 {
1426 if (!ppszValue)
1427 rc = VINF_SUCCESS;
1428 else
1429 {
1430 char *pszValue;
1431 rc = RTStrAllocEx(&pszValue, cchValue + 1);
1432 if (RT_SUCCESS(rc))
1433 {
1434 rc = ScmStreamRead(&Stream, pszValue, cchValue);
1435 if (RT_SUCCESS(rc))
1436 *ppszValue = pszValue;
1437 else
1438 RTStrFree(pszValue);
1439 }
1440 }
1441 break;
1442 }
1443
1444 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
1445 break;
1446 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1447 break;
1448 }
1449
1450 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
1451 {
1452 rc = ScmStreamGetStatus(&Stream);
1453 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
1454 }
1455 ScmStreamDelete(&Stream);
1456 }
1457 }
1458
1459 if (rc == VERR_FILE_NOT_FOUND)
1460 rc = VERR_NOT_FOUND;
1461 }
1462 else
1463 {
1464 const char *apszArgs[] = { g_szSvnPath, "propget", "--strict", pszName, pState->pszFilename, NULL };
1465 char *pszValue;
1466 rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszValue);
1467 if (RT_SUCCESS(rc))
1468 {
1469 if (pszValue && *pszValue)
1470 {
1471 if (ppszValue)
1472 {
1473 *ppszValue = pszValue;
1474 pszValue = NULL;
1475 }
1476 }
1477 else
1478 rc = VERR_NOT_FOUND;
1479 RTStrFree(pszValue);
1480 }
1481 }
1482 return rc;
1483}
1484
1485
1486/**
1487 * Queries the value of an SVN property on the parent dir/whatever.
1488 *
1489 * This will not adjust for scheduled changes to the parent!
1490 *
1491 * @returns IPRT status code.
1492 * @retval VERR_INVALID_STATE if not a SVN WC file.
1493 * @retval VERR_NOT_FOUND if the property wasn't found.
1494 * @param pState The rewrite state to work on.
1495 * @param pszName The property name.
1496 * @param ppszValue Where to return the property value. Free this
1497 * using RTStrFree. Optional.
1498 */
1499int ScmSvnQueryParentProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
1500{
1501 /*
1502 * Strip the filename and use ScmSvnQueryProperty.
1503 */
1504 char szPath[RTPATH_MAX];
1505 int rc = RTStrCopy(szPath, sizeof(szPath), pState->pszFilename);
1506 if (RT_SUCCESS(rc))
1507 {
1508 RTPathStripFilename(szPath);
1509 SCMRWSTATE ParentState;
1510 ParentState.pszFilename = szPath;
1511 ParentState.fFirst = false;
1512 ParentState.fIsInSvnWorkingCopy = true;
1513 ParentState.cSvnPropChanges = 0;
1514 ParentState.paSvnPropChanges = NULL;
1515 ParentState.rc = VINF_SUCCESS;
1516 rc = ScmSvnQueryProperty(&ParentState, pszName, ppszValue);
1517 if (RT_SUCCESS(rc))
1518 rc = ParentState.rc;
1519 }
1520 return rc;
1521}
1522
1523
1524/**
1525 * Schedules the setting of a property.
1526 *
1527 * @returns IPRT status code.
1528 * @retval VERR_INVALID_STATE if not a SVN WC file.
1529 * @param pState The rewrite state to work on.
1530 * @param pszName The name of the property to set.
1531 * @param pszValue The value. NULL means deleting it.
1532 */
1533int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
1534{
1535 /*
1536 * Update any existing entry first.
1537 */
1538 size_t i = pState->cSvnPropChanges;
1539 while (i-- > 0)
1540 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1541 {
1542 if (!pszValue)
1543 {
1544 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1545 pState->paSvnPropChanges[i].pszValue = NULL;
1546 }
1547 else
1548 {
1549 char *pszCopy;
1550 int rc = RTStrDupEx(&pszCopy, pszValue);
1551 if (RT_FAILURE(rc))
1552 return rc;
1553 pState->paSvnPropChanges[i].pszValue = pszCopy;
1554 }
1555 return VINF_SUCCESS;
1556 }
1557
1558 /*
1559 * Insert a new entry.
1560 */
1561 i = pState->cSvnPropChanges;
1562 if ((i % 32) == 0)
1563 {
1564 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
1565 if (!pvNew)
1566 return VERR_NO_MEMORY;
1567 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
1568 }
1569
1570 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
1571 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
1572 if ( pState->paSvnPropChanges[i].pszName
1573 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
1574 pState->cSvnPropChanges = i + 1;
1575 else
1576 {
1577 RTStrFree(pState->paSvnPropChanges[i].pszName);
1578 pState->paSvnPropChanges[i].pszName = NULL;
1579 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1580 pState->paSvnPropChanges[i].pszValue = NULL;
1581 return VERR_NO_MEMORY;
1582 }
1583 return VINF_SUCCESS;
1584}
1585
1586
1587/**
1588 * Schedules a property deletion.
1589 *
1590 * @returns IPRT status code.
1591 * @param pState The rewrite state to work on.
1592 * @param pszName The name of the property to delete.
1593 */
1594int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
1595{
1596 return ScmSvnSetProperty(pState, pszName, NULL);
1597}
1598
1599
1600/**
1601 * Applies any SVN property changes to the work copy of the file.
1602 *
1603 * @returns IPRT status code.
1604 * @param pState The rewrite state which SVN property changes
1605 * should be applied.
1606 */
1607int ScmSvnDisplayChanges(PSCMRWSTATE pState)
1608{
1609 size_t i = pState->cSvnPropChanges;
1610 while (i-- > 0)
1611 {
1612 const char *pszName = pState->paSvnPropChanges[i].pszName;
1613 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1614 if (pszValue)
1615 ScmVerbose(pState, 0, "svn propset '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
1616 else
1617 ScmVerbose(pState, 0, "svn propdel '%s' %s\n", pszName, pState->pszFilename);
1618 }
1619
1620 return VINF_SUCCESS;
1621}
1622
1623/**
1624 * Applies any SVN property changes to the work copy of the file.
1625 *
1626 * @returns IPRT status code.
1627 * @param pState The rewrite state which SVN property changes
1628 * should be applied.
1629 */
1630int ScmSvnApplyChanges(PSCMRWSTATE pState)
1631{
1632#ifdef SCM_WITH_LATER
1633 if (0)
1634 {
1635 return ...;
1636 }
1637
1638 /* Fallback: */
1639#endif
1640
1641 /*
1642 * Iterate thru the changes and apply them by starting the svn client.
1643 */
1644 for (size_t i = 0; i < pState->cSvnPropChanges; i++)
1645 {
1646 const char *apszArgv[6];
1647 apszArgv[0] = g_szSvnPath;
1648 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "propset" : "propdel";
1649 apszArgv[2] = pState->paSvnPropChanges[i].pszName;
1650 int iArg = 3;
1651 if (pState->paSvnPropChanges[i].pszValue)
1652 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
1653 apszArgv[iArg++] = pState->pszFilename;
1654 apszArgv[iArg++] = NULL;
1655
1656 int rc = scmSvnRun(pState, apszArgv, false);
1657 if (RT_FAILURE(rc))
1658 return rc;
1659 }
1660
1661 return VINF_SUCCESS;
1662}
1663
1664
1665/**
1666 * Initializes the subversion interface.
1667 */
1668void ScmSvnInit(void)
1669{
1670 scmSvnFindSvnBinary(NULL);
1671}
1672
1673
1674void ScmSvnTerm(void)
1675{
1676#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1677 scmSvnFlushClientContextAndPool();
1678#endif
1679}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use