/
io.c
5754 lines (4856 loc) · 184 KB
/
io.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* io.c: shared file reading, writing, and probing code.
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
#ifndef APR_STATUS_IS_EPERM
#include <errno.h>
#ifdef EPERM
#define APR_STATUS_IS_EPERM(s) ((s) == EPERM)
#else
#define APR_STATUS_IS_EPERM(s) (0)
#endif
#endif
#include <apr_lib.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_file_info.h>
#include <apr_general.h>
#include <apr_strings.h>
#include <apr_portable.h>
#include <apr_md5.h>
#if APR_HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include "svn_hash.h"
#include "svn_types.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_pools.h"
#include "svn_utf.h"
#include "svn_config.h"
#include "svn_private_config.h"
#include "svn_ctype.h"
#include "private/svn_atomic.h"
#include "private/svn_io_private.h"
#include "private/svn_utf_private.h"
#include "private/svn_dep_compat.h"
#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
/*
Windows is 'aided' by a number of types of applications that
follow other applications around and open up files they have
changed for various reasons (the most intrusive are virus
scanners). So, if one of these other apps has glommed onto
our file we may get an 'access denied' error.
This retry loop does not completely solve the problem (who
knows how long the other app is going to hold onto it for), but
goes a long way towards minimizing it. It is not an infinite
loop because there might really be an error.
Another reason for retrying delete operations on Windows
is that they are asynchronous -- the file or directory is not
actually deleted until the last handle to it is closed. The
retry loop cannot completely solve this problem either, but can
help mitigate it.
*/
#define RETRY_MAX_ATTEMPTS 100
#define RETRY_INITIAL_SLEEP 1000
#define RETRY_MAX_SLEEP 128000
#define RETRY_LOOP(err, expr, retry_test, sleep_test) \
do \
{ \
apr_status_t os_err = APR_TO_OS_ERROR(err); \
int sleep_count = RETRY_INITIAL_SLEEP; \
int retries; \
for (retries = 0; \
retries < RETRY_MAX_ATTEMPTS && (retry_test); \
os_err = APR_TO_OS_ERROR(err)) \
{ \
if (sleep_test) \
{ \
++retries; \
apr_sleep(sleep_count); \
if (sleep_count < RETRY_MAX_SLEEP) \
sleep_count *= 2; \
} \
(err) = (expr); \
} \
} \
while (0)
#if defined(EDEADLK) && APR_HAS_THREADS
#define FILE_LOCK_RETRY_LOOP(err, expr) \
RETRY_LOOP(err, \
expr, \
(APR_STATUS_IS_EINTR(err) || os_err == EDEADLK), \
(!APR_STATUS_IS_EINTR(err)))
#else
#define FILE_LOCK_RETRY_LOOP(err, expr) \
RETRY_LOOP(err, \
expr, \
(APR_STATUS_IS_EINTR(err)), \
0)
#endif
#ifndef WIN32_RETRY_LOOP
#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP)
#define WIN32_RETRY_LOOP(err, expr) \
RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED \
|| os_err == ERROR_SHARING_VIOLATION \
|| os_err == ERROR_DIR_NOT_EMPTY), \
1)
#else
#define WIN32_RETRY_LOOP(err, expr) ((void)0)
#endif
#endif
#ifdef WIN32
#if _WIN32_WINNT < 0x600 /* Does the SDK assume Windows Vista+? */
typedef struct _FILE_BASIC_INFO {
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
DWORD FileAttributes;
} FILE_BASIC_INFO, *PFILE_BASIC_INFO;
typedef struct _FILE_RENAME_INFO {
BOOL ReplaceIfExists;
HANDLE RootDirectory;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;
typedef struct _FILE_DISPOSITION_INFO {
BOOL DeleteFile;
} FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;
typedef struct _FILE_ATTRIBUTE_TAG_INFO {
DWORD FileAttributes;
DWORD ReparseTag;
} FILE_ATTRIBUTE_TAG_INFO, *PFILE_ATTRIBUTE_TAG_INFO;
#define FileBasicInfo 0
#define FileRenameInfo 3
#define FileDispositionInfo 4
#define FileAttributeTagInfo 9
#endif /* WIN32 < Vista */
/* One-time initialization of the late bound Windows API functions. */
static volatile svn_atomic_t win_dynamic_imports_state = 0;
/* Pointer to GetFinalPathNameByHandleW function from kernel32.dll. */
typedef DWORD (WINAPI *GETFINALPATHNAMEBYHANDLE)(
HANDLE hFile,
WCHAR *lpszFilePath,
DWORD cchFilePath,
DWORD dwFlags);
typedef BOOL (WINAPI *GetFileInformationByHandleEx_t)(HANDLE hFile,
int FileInformationClass,
LPVOID lpFileInformation,
DWORD dwBufferSize);
typedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile,
int FileInformationClass,
LPVOID lpFileInformation,
DWORD dwBufferSize);
static GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL;
static GetFileInformationByHandleEx_t get_file_information_by_handle_ex_proc = NULL;
static SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL;
/* Forward declarations. */
static svn_error_t * io_win_read_link(svn_string_t **dest,
const char *path,
apr_pool_t *pool);
static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p,
svn_boolean_t *is_symlink_p,
const char *path,
apr_pool_t *pool);
#endif
/* Forward declaration */
static apr_status_t
dir_is_empty(const char *dir, apr_pool_t *pool);
static APR_INLINE svn_error_t *
do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
const char *msg, const char *msg_no_name,
apr_pool_t *pool);
/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
* operating systems where APR always uses utf-8 as native path format */
static svn_error_t *
cstring_to_utf8(const char **path_utf8,
const char *path_apr,
apr_pool_t *pool)
{
#if defined(WIN32) || defined(DARWIN)
*path_utf8 = path_apr;
return SVN_NO_ERROR;
#else
return svn_path_cstring_to_utf8(path_utf8, path_apr, pool);
#endif
}
/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on
* operating systems where APR always uses utf-8 as native path format */
static svn_error_t *
cstring_from_utf8(const char **path_apr,
const char *path_utf8,
apr_pool_t *pool)
{
#if defined(WIN32) || defined(DARWIN)
*path_apr = path_utf8;
return SVN_NO_ERROR;
#else
return svn_path_cstring_from_utf8(path_apr, path_utf8, pool);
#endif
}
/* Helper function that allows to convert an APR-level PATH to something
* that we can pass the svn_error_wrap_apr. Since we use it in context
* of error reporting, having *some* path info may be more useful than
* having none. Therefore, we use a best effort approach here.
*
* This is different from svn_io_file_name_get in that it uses a different
* signature style and will never fail.
*/
static const char *
try_utf8_from_internal_style(const char *path, apr_pool_t *pool)
{
svn_error_t *error;
const char *path_utf8;
/* Special case. */
if (path == NULL)
return "(NULL)";
/* (try to) convert PATH to UTF-8. If that fails, continue with the plain
* PATH because it is the best we have. It may actually be UTF-8 already.
*/
error = cstring_to_utf8(&path_utf8, path, pool);
if (error)
{
/* fallback to best representation we have */
svn_error_clear(error);
path_utf8 = path;
}
/* Toggle (back-)slashes etc. as necessary.
*/
return svn_dirent_local_style(path_utf8, pool);
}
/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
* NAME is in the internal encoding used by APR; PARENT is in
* UTF-8 and in internal (not local) style.
*
* Use PARENT only for generating an error string if the conversion
* fails because NAME could not be represented in UTF-8. In that
* case, return a two-level error in which the outer error's message
* mentions PARENT, but the inner error's message does not mention
* NAME (except possibly in hex) since NAME may not be printable.
* Such a compound error at least allows the user to go looking in the
* right directory for the problem.
*
* If there is any other error, just return that error directly.
*
* If there is any error, the effect on *NAME_P is undefined.
*
* *NAME_P and NAME may refer to the same storage.
*/
static svn_error_t *
entry_name_to_utf8(const char **name_p,
const char *name,
const char *parent,
apr_pool_t *pool)
{
#if defined(WIN32) || defined(DARWIN)
*name_p = apr_pstrdup(pool, name);
return SVN_NO_ERROR;
#else
svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
if (err && err->apr_err == APR_EINVAL)
{
return svn_error_createf(err->apr_err, err,
_("Error converting entry "
"in directory '%s' to UTF-8"),
svn_dirent_local_style(parent, pool));
}
return err;
#endif
}
static void
map_apr_finfo_to_node_kind(svn_node_kind_t *kind,
svn_boolean_t *is_special,
apr_finfo_t *finfo)
{
*is_special = FALSE;
if (finfo->filetype == APR_REG)
*kind = svn_node_file;
else if (finfo->filetype == APR_DIR)
*kind = svn_node_dir;
else if (finfo->filetype == APR_LNK)
{
*is_special = TRUE;
*kind = svn_node_file;
}
else
*kind = svn_node_unknown;
}
/* Helper for svn_io_check_path() and svn_io_check_resolved_path();
essentially the same semantics as those two, with the obvious
interpretation for RESOLVE_SYMLINKS. */
static svn_error_t *
io_check_path(const char *path,
svn_boolean_t resolve_symlinks,
svn_boolean_t *is_special_p,
svn_node_kind_t *kind,
apr_pool_t *pool)
{
apr_int32_t flags;
apr_finfo_t finfo;
apr_status_t apr_err;
const char *path_apr;
svn_boolean_t is_special = FALSE;
if (path[0] == '\0')
path = ".";
/* Not using svn_io_stat() here because we want to check the
apr_err return explicitly. */
SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
apr_err = apr_stat(&finfo, path_apr, flags, pool);
if (APR_STATUS_IS_ENOENT(apr_err))
*kind = svn_node_none;
else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err))
*kind = svn_node_none;
else if (apr_err)
return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
svn_dirent_local_style(path, pool));
else
map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
*is_special_p = is_special;
return SVN_NO_ERROR;
}
/* Wrapper for apr_file_open(), taking an APR-encoded filename. */
static apr_status_t
file_open(apr_file_t **f,
const char *fname_apr,
apr_int32_t flag,
apr_fileperms_t perm,
svn_boolean_t retry_on_failure,
apr_pool_t *pool)
{
apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool);
if (retry_on_failure)
{
#ifdef WIN32
if (status == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED))
{
if ((flag & (APR_CREATE | APR_EXCL)) == (APR_CREATE | APR_EXCL))
return status; /* Can't create if there is something */
if (flag & (APR_WRITE | APR_CREATE))
{
apr_finfo_t finfo;
if (!apr_stat(&finfo, fname_apr, SVN__APR_FINFO_READONLY, pool))
{
if (finfo.protection & APR_FREADONLY)
return status; /* Retrying won't fix this */
}
}
}
#endif
WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool));
}
return status;
}
svn_error_t *
svn_io_check_resolved_path(const char *path,
svn_node_kind_t *kind,
apr_pool_t *pool)
{
#if WIN32
return io_win_check_path(kind, NULL, path, pool);
#else
svn_boolean_t ignored;
return io_check_path(path, TRUE, &ignored, kind, pool);
#endif
}
svn_error_t *
svn_io_check_path(const char *path,
svn_node_kind_t *kind,
apr_pool_t *pool)
{
#if WIN32
svn_boolean_t is_symlink;
SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
if (is_symlink)
*kind = svn_node_file;
return SVN_NO_ERROR;
#else
svn_boolean_t ignored;
return io_check_path(path, FALSE, &ignored, kind, pool);
#endif
}
svn_error_t *
svn_io_check_special_path(const char *path,
svn_node_kind_t *kind,
svn_boolean_t *is_special,
apr_pool_t *pool)
{
#ifdef WIN32
svn_boolean_t is_symlink;
SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
if (is_symlink)
{
*is_special = TRUE;
*kind = svn_node_file;
}
else
*is_special = FALSE;
return SVN_NO_ERROR;
#else
return io_check_path(path, FALSE, is_special, kind, pool);
#endif
}
struct temp_file_cleanup_s
{
apr_pool_t *pool;
/* The (APR-encoded) full path of the file to be removed, or NULL if
* nothing to do. */
const char *fname_apr;
};
static apr_status_t
temp_file_plain_cleanup_handler(void *baton)
{
struct temp_file_cleanup_s *b = baton;
apr_status_t apr_err = APR_SUCCESS;
if (b->fname_apr)
{
apr_err = apr_file_remove(b->fname_apr, b->pool);
WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool));
}
return apr_err;
}
static apr_status_t
temp_file_child_cleanup_handler(void *baton)
{
struct temp_file_cleanup_s *b = baton;
apr_pool_cleanup_kill(b->pool, b,
temp_file_plain_cleanup_handler);
return APR_SUCCESS;
}
svn_error_t *
svn_io_open_uniquely_named(apr_file_t **file,
const char **unique_path,
const char *dirpath,
const char *filename,
const char *suffix,
svn_io_file_del_t delete_when,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *path;
unsigned int i;
struct temp_file_cleanup_s *baton = NULL;
/* At the beginning, we don't know whether unique_path will need
UTF8 conversion */
svn_boolean_t needs_utf8_conversion = TRUE;
SVN_ERR_ASSERT(file || unique_path);
if (dirpath == NULL)
SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
if (filename == NULL)
filename = "tempfile";
if (suffix == NULL)
suffix = ".tmp";
path = svn_dirent_join(dirpath, filename, scratch_pool);
if (delete_when == svn_io_file_del_on_pool_cleanup)
{
baton = apr_palloc(result_pool, sizeof(*baton));
baton->pool = result_pool;
baton->fname_apr = NULL;
/* Because cleanups are run LIFO, we need to make sure to register
our cleanup before the apr_file_close cleanup:
On Windows, you can't remove an open file.
*/
apr_pool_cleanup_register(result_pool, baton,
temp_file_plain_cleanup_handler,
temp_file_child_cleanup_handler);
}
for (i = 1; i <= 99999; i++)
{
const char *unique_name;
const char *unique_name_apr;
apr_file_t *try_file;
apr_status_t apr_err;
apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
| APR_BUFFERED | APR_BINARY);
if (delete_when == svn_io_file_del_on_close)
flag |= APR_DELONCLOSE;
/* Special case the first attempt -- if we can avoid having a
generated numeric portion at all, that's best. So first we
try with just the suffix; then future tries add a number
before the suffix. (A do-while loop could avoid the repeated
conditional, but it's not worth the clarity loss.)
If the first attempt fails, the first number will be "2".
This is good, since "1" would misleadingly imply that
the second attempt was actually the first... and if someone's
got conflicts on their conflicts, we probably don't want to
add to their confusion :-). */
if (i == 1)
unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix);
else
unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix);
/* Hmmm. Ideally, we would append to a native-encoding buf
before starting iteration, then convert back to UTF-8 for
return. But I suppose that would make the appending code
sensitive to i18n in a way it shouldn't be... Oh well. */
if (needs_utf8_conversion)
{
SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name,
scratch_pool));
if (i == 1)
{
/* The variable parts of unique_name will not require UTF8
conversion. Therefore, if UTF8 conversion had no effect
on it in the first iteration, it won't require conversion
in any future iteration. */
needs_utf8_conversion = strcmp(unique_name_apr, unique_name);
}
}
else
unique_name_apr = unique_name;
apr_err = file_open(&try_file, unique_name_apr, flag,
APR_OS_DEFAULT, FALSE, result_pool);
if (APR_STATUS_IS_EEXIST(apr_err))
continue;
else if (apr_err)
{
/* On Win32, CreateFile fails with an "Access Denied" error
code, rather than "File Already Exists", if the colliding
name belongs to a directory. */
if (APR_STATUS_IS_EACCES(apr_err))
{
apr_finfo_t finfo;
apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
APR_FINFO_TYPE, scratch_pool);
if (!apr_err_2 && finfo.filetype == APR_DIR)
continue;
#ifdef WIN32
if (apr_err == APR_FROM_OS_ERROR(ERROR_ACCESS_DENIED) ||
apr_err == APR_FROM_OS_ERROR(ERROR_SHARING_VIOLATION))
{
/* The file is in use by another process or is hidden;
create a new name, but don't do this 99999 times in
case the folder is not writable */
i += 797;
continue;
}
#endif
/* Else fall through and return the original error. */
}
if (file)
*file = NULL;
if (unique_path)
*unique_path = NULL;
return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
svn_dirent_local_style(unique_name,
scratch_pool));
}
else
{
if (delete_when == svn_io_file_del_on_pool_cleanup)
baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr);
if (file)
*file = try_file;
else
apr_file_close(try_file);
if (unique_path)
*unique_path = apr_pstrdup(result_pool, unique_name);
return SVN_NO_ERROR;
}
}
if (file)
*file = NULL;
if (unique_path)
*unique_path = NULL;
return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
NULL,
_("Unable to make name for '%s'"),
svn_dirent_local_style(path, scratch_pool));
}
svn_error_t *
svn_io_create_unique_link(const char **unique_name_p,
const char *path,
const char *dest,
const char *suffix,
apr_pool_t *pool)
{
#ifdef HAVE_SYMLINK
unsigned int i;
const char *unique_name;
const char *unique_name_apr;
const char *dest_apr;
int rv;
SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool));
for (i = 1; i <= 99999; i++)
{
apr_status_t apr_err;
/* Special case the first attempt -- if we can avoid having a
generated numeric portion at all, that's best. So first we
try with just the suffix; then future tries add a number
before the suffix. (A do-while loop could avoid the repeated
conditional, but it's not worth the clarity loss.)
If the first attempt fails, the first number will be "2".
This is good, since "1" would misleadingly imply that
the second attempt was actually the first... and if someone's
got conflicts on their conflicts, we probably don't want to
add to their confusion :-). */
if (i == 1)
unique_name = apr_psprintf(pool, "%s%s", path, suffix);
else
unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
/* Hmmm. Ideally, we would append to a native-encoding buf
before starting iteration, then convert back to UTF-8 for
return. But I suppose that would make the appending code
sensitive to i18n in a way it shouldn't be... Oh well. */
SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool));
do {
rv = symlink(dest_apr, unique_name_apr);
} while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
apr_err = apr_get_os_error();
if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
continue;
else if (rv == -1 && apr_err)
{
/* On Win32, CreateFile fails with an "Access Denied" error
code, rather than "File Already Exists", if the colliding
name belongs to a directory. */
if (APR_STATUS_IS_EACCES(apr_err))
{
apr_finfo_t finfo;
apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
APR_FINFO_TYPE, pool);
if (!apr_err_2
&& (finfo.filetype == APR_DIR))
continue;
/* Else ignore apr_err_2; better to fall through and
return the original error. */
}
*unique_name_p = NULL;
return svn_error_wrap_apr(apr_err,
_("Can't create symbolic link '%s'"),
svn_dirent_local_style(unique_name, pool));
}
else
{
*unique_name_p = unique_name;
return SVN_NO_ERROR;
}
}
*unique_name_p = NULL;
return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
NULL,
_("Unable to make name for '%s'"),
svn_dirent_local_style(path, pool));
#else
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Symbolic links are not supported on this "
"platform"));
#endif
}
svn_error_t *
svn_io_read_link(svn_string_t **dest,
const char *path,
apr_pool_t *pool)
{
#if defined(HAVE_READLINK)
svn_string_t dest_apr;
const char *path_apr;
char buf[1025];
ssize_t rv;
SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
do {
rv = readlink(path_apr, buf, sizeof(buf) - 1);
} while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
if (rv == -1)
return svn_error_wrap_apr(apr_get_os_error(),
_("Can't read contents of link"));
buf[rv] = '\0';
dest_apr.data = buf;
dest_apr.len = rv;
/* ### Cast needed, one of these interfaces is wrong */
return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
#elif defined(WIN32)
return io_win_read_link(dest, path, pool);
#else
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Symbolic links are not supported on this "
"platform"));
#endif
}
svn_error_t *
svn_io_copy_link(const char *src,
const char *dst,
apr_pool_t *pool)
{
#ifdef HAVE_READLINK
svn_string_t *link_dest;
const char *dst_tmp;
/* Notice what the link is pointing at... */
SVN_ERR(svn_io_read_link(&link_dest, src, pool));
/* Make a tmp-link pointing at the same thing. */
SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
".tmp", pool));
/* Move the tmp-link to link. */
return svn_io_file_rename2(dst_tmp, dst, FALSE, pool);
#else
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Symbolic links are not supported on this "
"platform"));
#endif
}
/* Temporary directory name cache for svn_io_temp_dir() */
static volatile svn_atomic_t temp_dir_init_state = 0;
static const char *temp_dir;
/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */
static svn_error_t *
init_temp_dir(void *baton, apr_pool_t *scratch_pool)
{
/* Global pool for the temp path */
apr_pool_t *global_pool = svn_pool_create(NULL);
const char *dir;
apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool);
if (apr_err)
return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool));
dir = svn_dirent_internal_style(dir, scratch_pool);
SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_io_temp_dir(const char **dir,
apr_pool_t *pool)
{
SVN_ERR(svn_atomic__init_once(&temp_dir_init_state,
init_temp_dir, NULL, pool));
*dir = apr_pstrdup(pool, temp_dir);
return SVN_NO_ERROR;
}
/*** Creating, copying and appending files. ***/
/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
* allocations.
*
* NOTE: We don't use apr_copy_file() for this, since it takes filenames
* as parameters. Since we want to copy to a temporary file
* and rename for atomicity (see below), this would require an extra
* close/open pair, which can be expensive, especially on
* remote file systems.
*/
static apr_status_t
copy_contents(apr_file_t *from_file,
apr_file_t *to_file,
apr_pool_t *pool)
{
/* Copy bytes till the cows come home. */
while (1)
{
char buf[SVN__STREAM_CHUNK_SIZE];
apr_size_t bytes_this_time = sizeof(buf);
apr_status_t read_err;
apr_status_t write_err;
/* Read 'em. */
read_err = apr_file_read(from_file, buf, &bytes_this_time);
if (read_err && !APR_STATUS_IS_EOF(read_err))
{
return read_err;
}
/* Write 'em. */
write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
if (write_err)
{
return write_err;
}
if (read_err && APR_STATUS_IS_EOF(read_err))
{
/* Return the results of this close: an error, or success. */
return APR_SUCCESS;
}
}
/* NOTREACHED */
}
svn_error_t *
svn_io_copy_file(const char *src,
const char *dst,
svn_boolean_t copy_perms,
apr_pool_t *pool)
{
apr_file_t *from_file, *to_file;
apr_status_t apr_err;
const char *dst_tmp;
svn_error_t *err;
/* ### NOTE: sometimes src == dst. In this case, because we copy to a
### temporary file, and then rename over the top of the destination,
### the net result is resetting the permissions on src/dst.
###
### Note: specifically, this can happen during a switch when the desired
### permissions for a file change from one branch to another. See
### switch_tests 17.
###
### ... yes, we should avoid copying to the same file, and we should
### make the "reset perms" explicit. The switch *happens* to work
### because of this copy-to-temp-then-rename implementation. If it
### weren't for that, the switch would break.
*/
#ifdef CHECK_FOR_SAME_FILE
if (strcmp(src, dst) == 0)
return SVN_NO_ERROR;
#endif
SVN_ERR(svn_io_file_open(&from_file, src, APR_READ,
APR_OS_DEFAULT, pool));
/* For atomicity, we copy to a tmp file and then rename the tmp
file over the real destination. */
SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp,
svn_dirent_dirname(dst, pool),
svn_io_file_del_none, pool, pool));
apr_err = copy_contents(from_file, to_file, pool);
if (apr_err)
{
err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"),
svn_dirent_local_style(src, pool),
svn_dirent_local_style(dst_tmp, pool));
}
else
err = NULL;
err = svn_error_compose_create(err,
svn_io_file_close(from_file, pool));
err = svn_error_compose_create(err,
svn_io_file_close(to_file, pool));
if (err)
{
return svn_error_compose_create(
err,
svn_io_remove_file2(dst_tmp, TRUE, pool));
}
/* If copying perms, set the perms on dst_tmp now, so they will be
atomically inherited in the upcoming rename. But note that we
had to wait until now to set perms, because if they say
read-only, then we'd have failed filling dst_tmp's contents. */
if (copy_perms)
SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool));
return svn_error_trace(svn_io_file_rename2(dst_tmp, dst, FALSE, pool));
}