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                              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
298svn_error_t *
299svn_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/* */
334static svn_error_t *
335init_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. */
348static svn_error_t *
349init_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
395svn_error_t *
396svn_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
504svn_error_t *
505svn_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
519svn_error_t *
520svn_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
552svn_error_t *
553svn_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
573svn_error_t *
574svn_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