VirtualBox

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

Last change on this file since 57000 was 57000, checked in by vboxsync, 9 years ago

scm: Error formatting fixes.

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