1 /*
2 * adm_files.c: helper routines for handling files & dirs in the
3 * working copy administrative area (creating,
4 * deleting, opening, and closing). This is the only
5 * code that actually knows where administrative
6 * information is kept.
7 *
8 * ====================================================================
9 * Licensed to the Apache Software Foundation (ASF) under one
10 * or more contributor license agreements. See the NOTICE file
11 * distributed with this work for additional information
12 * regarding copyright ownership. The ASF licenses this file
13 * to you under the Apache License, Version 2.0 (the
14 * "License"); you may not use this file except in compliance
15 * with the License. You may obtain a copy of the License at
16 *
17 * http://www.apache.org/licenses/LICENSE-2.0
18 *
19 * Unless required by applicable law or agreed to in writing,
20 * software distributed under the License is distributed on an
21 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22 * KIND, either express or implied. See the License for the
23 * specific language governing permissions and limitations
24 * under the License.
25 * ====================================================================
26 */
27
28
29
30 #include <stdarg.h>
31 #include <apr_pools.h>
32 #include <apr_file_io.h>
33 #include <apr_strings.h>
34
35 #include "svn_types.h"
36 #include "svn_error.h"
37 #include "svn_io.h"
38 #include "svn_dirent_uri.h"
39 #include "svn_path.h"
40 #include "svn_hash.h"
41
42 #include "wc.h"
43 #include "adm_files.h"
44 #include "entries.h"
45 #include "lock.h"
46
47 #include "svn_private_config.h"
48 #include "private/svn_wc_private.h"
49
50
51 /*** File names in the adm area. ***/
52
53 /* The default name of the WC admin directory. This name is always
54 checked by svn_wc_is_adm_dir. */
55 static const char default_adm_dir_name[] = ".svn";
56
57 /* The name that is actually used for the WC admin directory. The
58 commonest case where this won't be the default is in Windows
59 ASP.NET development environments, which used to choke on ".svn". */
60 static const char *adm_dir_name = default_adm_dir_name;
61
62
63 svn_boolean_t
svn_wc_is_adm_dir(const char * name,apr_pool_t * pool)64 svn_wc_is_adm_dir(const char *name, apr_pool_t *pool)
65 {
66 return (0 == strcmp(name, adm_dir_name)
67 || 0 == strcmp(name, default_adm_dir_name));
68 }
69
70
71 const char *
svn_wc_get_adm_dir(apr_pool_t * pool)72 svn_wc_get_adm_dir(apr_pool_t *pool)
73 {
74 return adm_dir_name;
75 }
76
77
78 svn_error_t *
svn_wc_set_adm_dir(const char * name,apr_pool_t * pool)79 svn_wc_set_adm_dir(const char *name, apr_pool_t *pool)
80 {
81 /* This is the canonical list of administrative directory names.
82
83 FIXME:
84 An identical list is used in
85 libsvn_subr/opt.c:svn_opt__args_to_target_array(),
86 but that function can't use this list, because that use would
87 create a circular dependency between libsvn_wc and libsvn_subr.
88 Make sure changes to the lists are always synchronized! */
89 static const char *valid_dir_names[] = {
90 default_adm_dir_name,
91 "_svn",
92 NULL
93 };
94
95 const char **dir_name;
96 for (dir_name = valid_dir_names; *dir_name; ++dir_name)
97 if (0 == strcmp(name, *dir_name))
98 {
99 /* Use the pointer to the statically allocated string
100 constant, to avoid potential pool lifetime issues. */
101 adm_dir_name = *dir_name;
102 return SVN_NO_ERROR;
103 }
104 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
105 _("'%s' is not a valid administrative "
106 "directory name"),
107 svn_dirent_local_style(name, pool));
108 }
109
110
111 const char *
svn_wc__adm_child(const char * path,const char * child,apr_pool_t * result_pool)112 svn_wc__adm_child(const char *path,
113 const char *child,
114 apr_pool_t *result_pool)
115 {
116 return svn_dirent_join_many(result_pool,
117 path,
118 adm_dir_name,
119 child,
120 NULL);
121 }
122
123
124 svn_boolean_t
svn_wc__adm_area_exists(const char * adm_abspath,apr_pool_t * pool)125 svn_wc__adm_area_exists(const char *adm_abspath,
126 apr_pool_t *pool)
127 {
128 const char *path = svn_wc__adm_child(adm_abspath, NULL, pool);
129 svn_node_kind_t kind;
130 svn_error_t *err;
131
132 err = svn_io_check_path(path, &kind, pool);
133 if (err)
134 {
135 svn_error_clear(err);
136 /* Return early, since kind is undefined in this case. */
137 return FALSE;
138 }
139
140 return kind != svn_node_none;
141 }
142
143
144
145 /*** Making and using files in the adm area. ***/
146
147
148 /* */
149 static svn_error_t *
make_adm_subdir(const char * path,const char * subdir,apr_pool_t * pool)150 make_adm_subdir(const char *path,
151 const char *subdir,
152 apr_pool_t *pool)
153 {
154 const char *fullpath;
155
156 fullpath = svn_wc__adm_child(path, subdir, pool);
157
158 return svn_io_dir_make(fullpath, APR_OS_DEFAULT, pool);
159 }
160
161
162
163 /*** Syncing files in the adm area. ***/
164
165
166 svn_error_t *
svn_wc__text_base_path_to_read(const char ** result_abspath,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)167 svn_wc__text_base_path_to_read(const char **result_abspath,
168 svn_wc__db_t *db,
169 const char *local_abspath,
170 apr_pool_t *result_pool,
171 apr_pool_t *scratch_pool)
172 {
173 svn_wc__db_status_t status;
174 svn_node_kind_t kind;
175 const svn_checksum_t *checksum;
176
177 SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL,
178 &checksum, NULL, NULL, NULL,
179 db, local_abspath,
180 scratch_pool, scratch_pool));
181
182 /* Sanity */
183 if (kind != svn_node_file)
184 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
185 _("Can only get the pristine contents of files; "
186 "'%s' is not a file"),
187 svn_dirent_local_style(local_abspath,
188 scratch_pool));
189
190 if (status == svn_wc__db_status_not_present)
191 /* We know that the delete of this node has been committed.
192 This should be the same as if called on an unknown path. */
193 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
194 _("Cannot get the pristine contents of '%s' "
195 "because its delete is already committed"),
196 svn_dirent_local_style(local_abspath,
197 scratch_pool));
198 else if (status == svn_wc__db_status_server_excluded
199 || status == svn_wc__db_status_excluded
200 || status == svn_wc__db_status_incomplete)
201 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
202 _("Cannot get the pristine contents of '%s' "
203 "because it has an unexpected status"),
204 svn_dirent_local_style(local_abspath,
205 scratch_pool));
206
207 if (checksum == NULL)
208 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
209 _("Node '%s' has no pristine text"),
210 svn_dirent_local_style(local_abspath,
211 scratch_pool));
212 SVN_ERR(svn_wc__db_pristine_get_path(result_abspath, db, local_abspath,
213 checksum,
214 result_pool, scratch_pool));
215 return SVN_NO_ERROR;
216 }
217
218 svn_error_t *
svn_wc__get_pristine_contents(svn_stream_t ** contents,svn_filesize_t * size,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)219 svn_wc__get_pristine_contents(svn_stream_t **contents,
220 svn_filesize_t *size,
221 svn_wc__db_t *db,
222 const char *local_abspath,
223 apr_pool_t *result_pool,
224 apr_pool_t *scratch_pool)
225 {
226 svn_wc__db_status_t status;
227 svn_node_kind_t kind;
228 const svn_checksum_t *sha1_checksum;
229
230 if (size)
231 *size = SVN_INVALID_FILESIZE;
232
233 SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL,
234 &sha1_checksum, NULL, NULL, NULL,
235 db, local_abspath,
236 scratch_pool, scratch_pool));
237
238 /* Sanity */
239 if (kind != svn_node_file)
240 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
241 _("Can only get the pristine contents of files; "
242 "'%s' is not a file"),
243 svn_dirent_local_style(local_abspath,
244 scratch_pool));
245
246 if (status == svn_wc__db_status_added && !sha1_checksum)
247 {
248 /* Simply added. The pristine base does not exist. */
249 *contents = NULL;
250 return SVN_NO_ERROR;
251 }
252 else if (status == svn_wc__db_status_not_present)
253 /* We know that the delete of this node has been committed.
254 This should be the same as if called on an unknown path. */
255 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
256 _("Cannot get the pristine contents of '%s' "
257 "because its delete is already committed"),
258 svn_dirent_local_style(local_abspath,
259 scratch_pool));
260 else if (status == svn_wc__db_status_server_excluded
261 || status == svn_wc__db_status_excluded
262 || status == svn_wc__db_status_incomplete)
263 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
264 _("Cannot get the pristine contents of '%s' "
265 "because it has an unexpected status"),
266 svn_dirent_local_style(local_abspath,
267 scratch_pool));
268 if (sha1_checksum)
269 SVN_ERR(svn_wc__db_pristine_read(contents, size, db, local_abspath,
270 sha1_checksum,
271 result_pool, scratch_pool));
272 else
273 *contents = NULL;
274
275 return SVN_NO_ERROR;
276 }
277
278
279 /*** Opening and closing files in the adm area. ***/
280
281 svn_error_t *
svn_wc__open_adm_stream(svn_stream_t ** stream,const char * dir_abspath,const char * fname,apr_pool_t * result_pool,apr_pool_t * scratch_pool)282 svn_wc__open_adm_stream(svn_stream_t **stream,
283 const char *dir_abspath,
284 const char *fname,
285 apr_pool_t *result_pool,
286 apr_pool_t *scratch_pool)
287 {
288 const char *local_abspath;
289
290 SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
291
292 local_abspath = svn_wc__adm_child(dir_abspath, fname, scratch_pool);
293 return svn_error_trace(svn_stream_open_readonly(stream, local_abspath,
294 result_pool, scratch_pool));
295 }
296
297
298 svn_error_t *
svn_wc__open_writable_base(svn_stream_t ** stream,const char ** temp_base_abspath,svn_checksum_t ** md5_checksum,svn_checksum_t ** sha1_checksum,svn_wc__db_t * db,const char * wri_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)299 svn_wc__open_writable_base(svn_stream_t **stream,
300 const char **temp_base_abspath,
301 svn_checksum_t **md5_checksum,
302 svn_checksum_t **sha1_checksum,
303 svn_wc__db_t *db,
304 const char *wri_abspath,
305 apr_pool_t *result_pool,
306 apr_pool_t *scratch_pool)
307 {
308 const char *temp_dir_abspath;
309 SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
310
311 SVN_ERR(svn_wc__db_pristine_get_tempdir(&temp_dir_abspath, db, wri_abspath,
312 scratch_pool, scratch_pool));
313 SVN_ERR(svn_stream_open_unique(stream,
314 temp_base_abspath,
315 temp_dir_abspath,
316 svn_io_file_del_none,
317 result_pool, scratch_pool));
318 if (md5_checksum)
319 *stream = svn_stream_checksummed2(*stream, NULL, md5_checksum,
320 svn_checksum_md5, FALSE, result_pool);
321 if (sha1_checksum)
322 *stream = svn_stream_checksummed2(*stream, NULL, sha1_checksum,
323 svn_checksum_sha1, FALSE, result_pool);
324
325 return SVN_NO_ERROR;
326 }
327
328
329
330 /*** Checking for and creating administrative subdirs. ***/
331
332
333 /* */
334 static svn_error_t *
init_adm_tmp_area(const char * path,apr_pool_t * pool)335 init_adm_tmp_area(const char *path, apr_pool_t *pool)
336 {
337 /* SVN_WC__ADM_TMP */
338 SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, pool));
339
340 return SVN_NO_ERROR;
341 }
342
343
344 /* Set up a new adm area for PATH, with REPOS_* as the repos info, and
345 INITIAL_REV as the starting revision. The entries file starts out
346 marked as 'incomplete. The adm area starts out locked; remember to
347 unlock it when done. */
348 static svn_error_t *
init_adm(svn_wc__db_t * db,const char * local_abspath,const char * repos_relpath,const char * repos_root_url,const char * repos_uuid,svn_revnum_t initial_rev,svn_depth_t depth,apr_pool_t * pool)349 init_adm(svn_wc__db_t *db,
350 const char *local_abspath,
351 const char *repos_relpath,
352 const char *repos_root_url,
353 const char *repos_uuid,
354 svn_revnum_t initial_rev,
355 svn_depth_t depth,
356 apr_pool_t *pool)
357 {
358 /* First, make an empty administrative area. */
359 SVN_ERR(svn_io_dir_make_hidden(svn_wc__adm_child(local_abspath, NULL, pool),
360 APR_OS_DEFAULT, pool));
361
362 /** Make subdirectories. ***/
363
364 /* SVN_WC__ADM_PRISTINE */
365 SVN_ERR(make_adm_subdir(local_abspath, SVN_WC__ADM_PRISTINE, pool));
366
367 /* ### want to add another directory? do a format bump to ensure that
368 ### all existing working copies get the new directories. or maybe
369 ### create-on-demand (more expensive) */
370
371 /** Init the tmp area. ***/
372 SVN_ERR(init_adm_tmp_area(local_abspath, pool));
373
374 /* Create the SDB. */
375 SVN_ERR(svn_wc__db_init(db, local_abspath,
376 repos_relpath, repos_root_url, repos_uuid,
377 initial_rev, depth,
378 pool));
379
380 /* Stamp ENTRIES and FORMAT files for old clients. */
381 SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath,
382 SVN_WC__ADM_ENTRIES,
383 pool),
384 SVN_WC__NON_ENTRIES_STRING,
385 pool));
386 SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath,
387 SVN_WC__ADM_FORMAT,
388 pool),
389 SVN_WC__NON_ENTRIES_STRING,
390 pool));
391
392 return SVN_NO_ERROR;
393 }
394
395 svn_error_t *
svn_wc__internal_ensure_adm(svn_wc__db_t * db,const char * local_abspath,const char * url,const char * repos_root_url,const char * repos_uuid,svn_revnum_t revision,svn_depth_t depth,apr_pool_t * scratch_pool)396 svn_wc__internal_ensure_adm(svn_wc__db_t *db,
397 const char *local_abspath,
398 const char *url,
399 const char *repos_root_url,
400 const char *repos_uuid,
401 svn_revnum_t revision,
402 svn_depth_t depth,
403 apr_pool_t *scratch_pool)
404 {
405 int format;
406 const char *original_repos_relpath;
407 const char *original_root_url;
408 svn_boolean_t is_op_root;
409 const char *repos_relpath = svn_uri_skip_ancestor(repos_root_url, url,
410 scratch_pool);
411 svn_wc__db_status_t status;
412 const char *db_repos_relpath, *db_repos_root_url, *db_repos_uuid;
413 svn_revnum_t db_revision;
414
415 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
416 SVN_ERR_ASSERT(url != NULL);
417 SVN_ERR_ASSERT(repos_root_url != NULL);
418 SVN_ERR_ASSERT(repos_uuid != NULL);
419 SVN_ERR_ASSERT(repos_relpath != NULL);
420
421 SVN_ERR(svn_wc__internal_check_wc(&format, db, local_abspath, TRUE,
422 scratch_pool));
423
424 /* Early out: we know we're not dealing with an existing wc, so
425 just create one. */
426 if (format == 0)
427 return svn_error_trace(init_adm(db, local_abspath,
428 repos_relpath, repos_root_url, repos_uuid,
429 revision, depth, scratch_pool));
430
431 SVN_ERR(svn_wc__db_read_info(&status, NULL,
432 &db_revision, &db_repos_relpath,
433 &db_repos_root_url, &db_repos_uuid,
434 NULL, NULL, NULL, NULL, NULL, NULL,
435 &original_repos_relpath, &original_root_url,
436 NULL, NULL, NULL, NULL, NULL, NULL,
437 NULL, &is_op_root, NULL, NULL,
438 NULL, NULL, NULL,
439 db, local_abspath, scratch_pool, scratch_pool));
440
441 /* When the directory exists and is scheduled for deletion or is not-present
442 * do not check the revision or the URL. The revision can be any
443 * arbitrary revision and the URL may differ if the add is
444 * being driven from a merge which will have a different URL. */
445 if (status != svn_wc__db_status_deleted
446 && status != svn_wc__db_status_not_present)
447 {
448 /* ### Should we match copyfrom_revision? */
449 if (db_revision != revision)
450 return
451 svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
452 _("Revision %ld doesn't match existing "
453 "revision %ld in '%s'"),
454 revision, db_revision, local_abspath);
455
456 if (!db_repos_root_url)
457 {
458 if (status == svn_wc__db_status_added)
459 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
460 &db_repos_relpath,
461 &db_repos_root_url,
462 &db_repos_uuid,
463 NULL, NULL, NULL, NULL,
464 db, local_abspath,
465 scratch_pool, scratch_pool));
466 else
467 SVN_ERR(svn_wc__db_scan_base_repos(&db_repos_relpath,
468 &db_repos_root_url,
469 &db_repos_uuid,
470 db, local_abspath,
471 scratch_pool, scratch_pool));
472 }
473
474 /* The caller gives us a URL which should match the entry. However,
475 some callers compensate for an old problem in entry->url and pass
476 the copyfrom_url instead. See ^/notes/api-errata/1.7/wc002.txt. As
477 a result, we allow the passed URL to match copyfrom_url if it
478 does not match the entry's primary URL. */
479 if (strcmp(db_repos_uuid, repos_uuid)
480 || strcmp(db_repos_root_url, repos_root_url)
481 || !svn_relpath_skip_ancestor(db_repos_relpath, repos_relpath))
482 {
483 if (!is_op_root /* copy_from was set on op-roots only */
484 || original_root_url == NULL
485 || strcmp(original_root_url, repos_root_url)
486 || strcmp(original_repos_relpath, repos_relpath))
487 return
488 svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
489 _("URL '%s' (uuid: '%s') doesn't match existing "
490 "URL '%s' (uuid: '%s') in '%s'"),
491 url,
492 db_repos_uuid,
493 svn_path_url_add_component2(db_repos_root_url,
494 db_repos_relpath,
495 scratch_pool),
496 repos_uuid,
497 local_abspath);
498 }
499 }
500
501 return SVN_NO_ERROR;
502 }
503
504 svn_error_t *
svn_wc_ensure_adm4(svn_wc_context_t * wc_ctx,const char * local_abspath,const char * url,const char * repos_root_url,const char * repos_uuid,svn_revnum_t revision,svn_depth_t depth,apr_pool_t * scratch_pool)505 svn_wc_ensure_adm4(svn_wc_context_t *wc_ctx,
506 const char *local_abspath,
507 const char *url,
508 const char *repos_root_url,
509 const char *repos_uuid,
510 svn_revnum_t revision,
511 svn_depth_t depth,
512 apr_pool_t *scratch_pool)
513 {
514 return svn_error_trace(
515 svn_wc__internal_ensure_adm(wc_ctx->db, local_abspath, url, repos_root_url,
516 repos_uuid, revision, depth, scratch_pool));
517 }
518
519 svn_error_t *
svn_wc__adm_destroy(svn_wc__db_t * db,const char * dir_abspath,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)520 svn_wc__adm_destroy(svn_wc__db_t *db,
521 const char *dir_abspath,
522 svn_cancel_func_t cancel_func,
523 void *cancel_baton,
524 apr_pool_t *scratch_pool)
525 {
526 svn_boolean_t is_wcroot;
527
528 SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
529
530 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
531
532 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool));
533
534 /* Well, the coast is clear for blowing away the administrative
535 directory, which also removes remaining locks */
536
537 /* Now close the DB, and we can delete the working copy */
538 if (is_wcroot)
539 {
540 SVN_ERR(svn_wc__db_drop_root(db, dir_abspath, scratch_pool));
541 SVN_ERR(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, NULL,
542 scratch_pool),
543 FALSE,
544 cancel_func, cancel_baton,
545 scratch_pool));
546 }
547
548 return SVN_NO_ERROR;
549 }
550
551
552 svn_error_t *
svn_wc__adm_cleanup_tmp_area(svn_wc__db_t * db,const char * adm_abspath,apr_pool_t * scratch_pool)553 svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db,
554 const char *adm_abspath,
555 apr_pool_t *scratch_pool)
556 {
557 const char *tmp_path;
558
559 SVN_ERR_ASSERT(svn_dirent_is_absolute(adm_abspath));
560
561 SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool));
562
563 /* Get the path to the tmp area, and blow it away. */
564 tmp_path = svn_wc__adm_child(adm_abspath, SVN_WC__ADM_TMP, scratch_pool);
565
566 SVN_ERR(svn_io_remove_dir2(tmp_path, TRUE, NULL, NULL, scratch_pool));
567
568 /* Now, rebuild the tmp area. */
569 return svn_error_trace(init_adm_tmp_area(adm_abspath, scratch_pool));
570 }
571
572
573 svn_error_t *
svn_wc__get_tmpdir(const char ** tmpdir_abspath,svn_wc_context_t * wc_ctx,const char * wri_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)574 svn_wc__get_tmpdir(const char **tmpdir_abspath,
575 svn_wc_context_t *wc_ctx,
576 const char *wri_abspath,
577 apr_pool_t *result_pool,
578 apr_pool_t *scratch_pool)
579 {
580 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(tmpdir_abspath,
581 wc_ctx->db, wri_abspath,
582 result_pool, scratch_pool));
583 return SVN_NO_ERROR;
584 }
585