VirtualBox

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

Last change on this file since 100714 was 99775, checked in by vboxsync, 19 months ago

*: Mark functions as static if not used outside of a given compilation unit. Enables the compiler to optimize inlining, reduces the symbol tables, exposes unused functions and in some rare cases exposes mismtaches between function declarations and definitions, but most importantly reduces the number of parfait reports for the extern-function-no-forward-declaration category. This should not result in any functional changes, bugref:3409

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