adm_files.c revision 299742
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. */
55static 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". */
60static const char *adm_dir_name = default_adm_dir_name;
61
62
63svn_boolean_t
64svn_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
71const char *
72svn_wc_get_adm_dir(apr_pool_t *pool)
73{
74  return adm_dir_name;
75}
76
77
78svn_error_t *
79svn_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
111const char *
112svn_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                              SVN_VA_NULL);
121}
122
123
124svn_boolean_t
125svn_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/* */
149static svn_error_t *
150make_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
166svn_error_t *
167svn_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
218svn_error_t *
219svn_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
281svn_error_t *
282svn_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/*** Checking for and creating administrative subdirs. ***/
299
300
301/* */
302static svn_error_t *
303init_adm_tmp_area(const char *path, apr_pool_t *pool)
304{
305  /* SVN_WC__ADM_TMP */
306  SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, pool));
307
308  return SVN_NO_ERROR;
309}
310
311
312/* Set up a new adm area for PATH, with REPOS_* as the repos info, and
313   INITIAL_REV as the starting revision.  The entries file starts out
314   marked as 'incomplete.  The adm area starts out locked; remember to
315   unlock it when done. */
316static svn_error_t *
317init_adm(svn_wc__db_t *db,
318         const char *local_abspath,
319         const char *repos_relpath,
320         const char *repos_root_url,
321         const char *repos_uuid,
322         svn_revnum_t initial_rev,
323         svn_depth_t depth,
324         apr_pool_t *pool)
325{
326  /* First, make an empty administrative area. */
327  SVN_ERR(svn_io_dir_make_hidden(svn_wc__adm_child(local_abspath, NULL, pool),
328                                 APR_OS_DEFAULT, pool));
329
330  /** Make subdirectories. ***/
331
332  /* SVN_WC__ADM_PRISTINE */
333  SVN_ERR(make_adm_subdir(local_abspath, SVN_WC__ADM_PRISTINE, pool));
334
335  /* ### want to add another directory? do a format bump to ensure that
336     ### all existing working copies get the new directories. or maybe
337     ### create-on-demand (more expensive)  */
338
339  /** Init the tmp area. ***/
340  SVN_ERR(init_adm_tmp_area(local_abspath, pool));
341
342  /* Create the SDB. */
343  SVN_ERR(svn_wc__db_init(db, local_abspath,
344                          repos_relpath, repos_root_url, repos_uuid,
345                          initial_rev, depth,
346                          pool));
347
348  /* Stamp ENTRIES and FORMAT files for old clients.  */
349  SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath,
350                                               SVN_WC__ADM_ENTRIES,
351                                               pool),
352                             SVN_WC__NON_ENTRIES_STRING,
353                             pool));
354  SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath,
355                                               SVN_WC__ADM_FORMAT,
356                                               pool),
357                             SVN_WC__NON_ENTRIES_STRING,
358                             pool));
359
360  return SVN_NO_ERROR;
361}
362
363svn_error_t *
364svn_wc__internal_ensure_adm(svn_wc__db_t *db,
365                            const char *local_abspath,
366                            const char *url,
367                            const char *repos_root_url,
368                            const char *repos_uuid,
369                            svn_revnum_t revision,
370                            svn_depth_t depth,
371                            apr_pool_t *scratch_pool)
372{
373  int format;
374  const char *original_repos_relpath;
375  const char *original_root_url;
376  svn_boolean_t is_op_root;
377  const char *repos_relpath = svn_uri_skip_ancestor(repos_root_url, url,
378                                                    scratch_pool);
379  svn_wc__db_status_t status;
380  const char *db_repos_relpath, *db_repos_root_url, *db_repos_uuid;
381  svn_revnum_t db_revision;
382
383  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
384  SVN_ERR_ASSERT(url != NULL);
385  SVN_ERR_ASSERT(repos_root_url != NULL);
386  SVN_ERR_ASSERT(repos_uuid != NULL);
387  SVN_ERR_ASSERT(repos_relpath != NULL);
388
389  SVN_ERR(svn_wc__internal_check_wc(&format, db, local_abspath, TRUE,
390                                    scratch_pool));
391
392  /* Early out: we know we're not dealing with an existing wc, so
393     just create one. */
394  if (format == 0)
395    return svn_error_trace(init_adm(db, local_abspath,
396                                    repos_relpath, repos_root_url, repos_uuid,
397                                    revision, depth, scratch_pool));
398
399  SVN_ERR(svn_wc__db_read_info(&status, NULL,
400                               &db_revision, &db_repos_relpath,
401                               &db_repos_root_url, &db_repos_uuid,
402                               NULL, NULL, NULL, NULL, NULL, NULL,
403                               &original_repos_relpath, &original_root_url,
404                               NULL, NULL, NULL, NULL, NULL, NULL,
405                               NULL, &is_op_root, NULL, NULL,
406                               NULL, NULL, NULL,
407                               db, local_abspath, scratch_pool, scratch_pool));
408
409  /* When the directory exists and is scheduled for deletion or is not-present
410   * do not check the revision or the URL.  The revision can be any
411   * arbitrary revision and the URL may differ if the add is
412   * being driven from a merge which will have a different URL. */
413  if (status != svn_wc__db_status_deleted
414      && status != svn_wc__db_status_not_present)
415    {
416      /* ### Should we match copyfrom_revision? */
417      if (db_revision != revision)
418        return
419          svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
420                            _("Revision %ld doesn't match existing "
421                              "revision %ld in '%s'"),
422                            revision, db_revision, local_abspath);
423
424      if (!db_repos_root_url)
425        {
426          if (status == svn_wc__db_status_added)
427            SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
428                                             &db_repos_relpath,
429                                             &db_repos_root_url,
430                                             &db_repos_uuid,
431                                             NULL, NULL, NULL, NULL,
432                                             db, local_abspath,
433                                             scratch_pool, scratch_pool));
434          else
435            SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL,
436                                             &db_repos_relpath,
437                                             &db_repos_root_url,
438                                             &db_repos_uuid, NULL, NULL, NULL,
439                                             NULL, NULL, NULL, NULL, NULL,
440                                             NULL, NULL,
441                                             db, local_abspath,
442                                             scratch_pool, scratch_pool));
443        }
444
445      /* The caller gives us a URL which should match the entry. However,
446         some callers compensate for an old problem in entry->url and pass
447         the copyfrom_url instead. See ^/notes/api-errata/1.7/wc002.txt. As
448         a result, we allow the passed URL to match copyfrom_url if it
449         does not match the entry's primary URL.  */
450      if (strcmp(db_repos_uuid, repos_uuid)
451          || strcmp(db_repos_root_url, repos_root_url)
452          || !svn_relpath_skip_ancestor(db_repos_relpath, repos_relpath))
453        {
454          if (!is_op_root /* copy_from was set on op-roots only */
455              || original_root_url == NULL
456              || strcmp(original_root_url, repos_root_url)
457              || strcmp(original_repos_relpath, repos_relpath))
458            return
459              svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
460                                _("URL '%s' (uuid: '%s') doesn't match existing "
461                                  "URL '%s' (uuid: '%s') in '%s'"),
462                                url,
463                                db_repos_uuid,
464                                svn_path_url_add_component2(db_repos_root_url,
465                                                            db_repos_relpath,
466                                                            scratch_pool),
467                                repos_uuid,
468                                local_abspath);
469        }
470    }
471
472  return SVN_NO_ERROR;
473}
474
475svn_error_t *
476svn_wc_ensure_adm4(svn_wc_context_t *wc_ctx,
477                   const char *local_abspath,
478                   const char *url,
479                   const char *repos_root_url,
480                   const char *repos_uuid,
481                   svn_revnum_t revision,
482                   svn_depth_t depth,
483                   apr_pool_t *scratch_pool)
484{
485  return svn_error_trace(
486    svn_wc__internal_ensure_adm(wc_ctx->db, local_abspath, url, repos_root_url,
487                                repos_uuid, revision, depth, scratch_pool));
488}
489
490svn_error_t *
491svn_wc__adm_destroy(svn_wc__db_t *db,
492                    const char *dir_abspath,
493                    svn_cancel_func_t cancel_func,
494                    void *cancel_baton,
495                    apr_pool_t *scratch_pool)
496{
497  svn_boolean_t is_wcroot;
498
499  SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
500
501  SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
502
503  SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool));
504
505  /* Well, the coast is clear for blowing away the administrative
506     directory, which also removes remaining locks */
507
508  /* Now close the DB, and we can delete the working copy */
509  if (is_wcroot)
510    {
511      SVN_ERR(svn_wc__db_drop_root(db, dir_abspath, scratch_pool));
512      SVN_ERR(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, NULL,
513                                                   scratch_pool),
514                                 FALSE,
515                                 cancel_func, cancel_baton,
516                                 scratch_pool));
517    }
518
519  return SVN_NO_ERROR;
520}
521
522
523svn_error_t *
524svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db,
525                             const char *adm_abspath,
526                             apr_pool_t *scratch_pool)
527{
528  const char *tmp_path;
529
530  SVN_ERR_ASSERT(svn_dirent_is_absolute(adm_abspath));
531
532  SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool));
533
534  /* Get the path to the tmp area, and blow it away. */
535  tmp_path = svn_wc__adm_child(adm_abspath, SVN_WC__ADM_TMP, scratch_pool);
536
537  SVN_ERR(svn_io_remove_dir2(tmp_path, TRUE, NULL, NULL, scratch_pool));
538
539  /* Now, rebuild the tmp area. */
540  return svn_error_trace(init_adm_tmp_area(adm_abspath, scratch_pool));
541}
542
543
544svn_error_t *
545svn_wc__get_tmpdir(const char **tmpdir_abspath,
546                   svn_wc_context_t *wc_ctx,
547                   const char *wri_abspath,
548                   apr_pool_t *result_pool,
549                   apr_pool_t *scratch_pool)
550{
551  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(tmpdir_abspath,
552                                         wc_ctx->db, wri_abspath,
553                                         result_pool, scratch_pool));
554  return SVN_NO_ERROR;
555}
556