stat.c revision 299742
1/*
2 * stat.c :  file and directory stat and read functions
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_want.h>
28
29#include <serf.h>
30
31#include "svn_private_config.h"
32#include "svn_pools.h"
33#include "svn_xml.h"
34#include "../libsvn_ra/ra_loader.h"
35#include "svn_config.h"
36#include "svn_dirent_uri.h"
37#include "svn_hash.h"
38#include "svn_path.h"
39#include "svn_props.h"
40#include "svn_time.h"
41#include "svn_version.h"
42
43#include "private/svn_dav_protocol.h"
44#include "private/svn_dep_compat.h"
45#include "private/svn_fspath.h"
46
47#include "ra_serf.h"
48
49
50
51/* Implements svn_ra__vtable_t.check_path(). */
52svn_error_t *
53svn_ra_serf__check_path(svn_ra_session_t *ra_session,
54                        const char *relpath,
55                        svn_revnum_t revision,
56                        svn_node_kind_t *kind,
57                        apr_pool_t *scratch_pool)
58{
59  svn_ra_serf__session_t *session = ra_session->priv;
60  apr_hash_t *props;
61  svn_error_t *err;
62  const char *url;
63
64  url = session->session_url.path;
65
66  /* If we have a relative path, append it. */
67  if (relpath)
68    url = svn_path_url_add_component2(url, relpath, scratch_pool);
69
70  /* If we were given a specific revision, get a URL that refers to that
71     specific revision (rather than floating with HEAD).  */
72  if (SVN_IS_VALID_REVNUM(revision))
73    {
74      SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
75                                          session,
76                                          url, revision,
77                                          scratch_pool, scratch_pool));
78    }
79
80  /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
81     Or we started with SVN_INVALID_REVNUM and URL may be floating.  */
82  err = svn_ra_serf__fetch_node_props(&props, session,
83                                      url, SVN_INVALID_REVNUM,
84                                      check_path_props,
85                                      scratch_pool, scratch_pool);
86
87  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
88    {
89      svn_error_clear(err);
90      *kind = svn_node_none;
91    }
92  else
93    {
94      apr_hash_t *dav_props;
95      const char *res_type;
96
97      /* Any other error, raise to caller. */
98      SVN_ERR(err);
99
100      dav_props = apr_hash_get(props, "DAV:", 4);
101      res_type = svn_prop_get_value(dav_props, "resourcetype");
102      if (!res_type)
103        {
104          /* How did this happen? */
105          return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
106                                 _("The PROPFIND response did not include the "
107                                   "requested resourcetype value"));
108        }
109
110      if (strcmp(res_type, "collection") == 0)
111        *kind = svn_node_dir;
112      else
113        *kind = svn_node_file;
114    }
115
116  return SVN_NO_ERROR;
117}
118
119
120/* Baton for fill_dirent_propfunc() */
121struct fill_dirent_baton_t
122{
123  /* Update the fields in this entry.  */
124  svn_dirent_t *entry;
125
126  svn_tristate_t *supports_deadprop_count;
127
128  /* If allocations are necessary, then use this pool.  */
129  apr_pool_t *result_pool;
130};
131
132/* Implements svn_ra_serf__prop_func_t */
133static svn_error_t *
134fill_dirent_propfunc(void *baton,
135                     const char *path,
136                     const char *ns,
137                     const char *name,
138                     const svn_string_t *val,
139                     apr_pool_t *scratch_pool)
140{
141  struct fill_dirent_baton_t *fdb = baton;
142
143  if (strcmp(ns, "DAV:") == 0)
144    {
145      if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
146        {
147          apr_int64_t rev;
148          SVN_ERR(svn_cstring_atoi64(&rev, val->data));
149
150          fdb->entry->created_rev = (svn_revnum_t)rev;
151        }
152      else if (strcmp(name, "creator-displayname") == 0)
153        {
154          fdb->entry->last_author = apr_pstrdup(fdb->result_pool, val->data);
155        }
156      else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
157        {
158          SVN_ERR(svn_time_from_cstring(&fdb->entry->time,
159                                        val->data,
160                                        fdb->result_pool));
161        }
162      else if (strcmp(name, "getcontentlength") == 0)
163        {
164          /* 'getcontentlength' property is empty for directories. */
165          if (val->len)
166            {
167              SVN_ERR(svn_cstring_atoi64(&fdb->entry->size, val->data));
168            }
169        }
170      else if (strcmp(name, "resourcetype") == 0)
171        {
172          if (strcmp(val->data, "collection") == 0)
173            {
174              fdb->entry->kind = svn_node_dir;
175            }
176          else
177            {
178              fdb->entry->kind = svn_node_file;
179            }
180        }
181    }
182  else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
183    {
184      fdb->entry->has_props = TRUE;
185    }
186  else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
187    {
188      fdb->entry->has_props = TRUE;
189    }
190  else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
191    {
192      if(strcmp(name, "deadprop-count") == 0)
193        {
194          if (*val->data)
195            {
196              apr_int64_t deadprop_count;
197              SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
198              fdb->entry->has_props = deadprop_count > 0;
199              if (fdb->supports_deadprop_count)
200                *fdb->supports_deadprop_count = svn_tristate_true;
201            }
202          else if (fdb->supports_deadprop_count)
203            *fdb->supports_deadprop_count = svn_tristate_false;
204        }
205    }
206
207  return SVN_NO_ERROR;
208}
209
210static const svn_ra_serf__dav_props_t *
211get_dirent_props(apr_uint32_t dirent_fields,
212                 svn_ra_serf__session_t *session,
213                 apr_pool_t *pool)
214{
215  svn_ra_serf__dav_props_t *prop;
216  apr_array_header_t *props = apr_array_make
217    (pool, 7, sizeof(svn_ra_serf__dav_props_t));
218
219  if (session->supports_deadprop_count != svn_tristate_false
220      || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
221    {
222      if (dirent_fields & SVN_DIRENT_KIND)
223        {
224          prop = apr_array_push(props);
225          prop->xmlns = "DAV:";
226          prop->name = "resourcetype";
227        }
228
229      if (dirent_fields & SVN_DIRENT_SIZE)
230        {
231          prop = apr_array_push(props);
232          prop->xmlns = "DAV:";
233          prop->name = "getcontentlength";
234        }
235
236      if (dirent_fields & SVN_DIRENT_HAS_PROPS)
237        {
238          prop = apr_array_push(props);
239          prop->xmlns = SVN_DAV_PROP_NS_DAV;
240          prop->name = "deadprop-count";
241        }
242
243      if (dirent_fields & SVN_DIRENT_CREATED_REV)
244        {
245          svn_ra_serf__dav_props_t *p = apr_array_push(props);
246          p->xmlns = "DAV:";
247          p->name = SVN_DAV__VERSION_NAME;
248        }
249
250      if (dirent_fields & SVN_DIRENT_TIME)
251        {
252          prop = apr_array_push(props);
253          prop->xmlns = "DAV:";
254          prop->name = SVN_DAV__CREATIONDATE;
255        }
256
257      if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
258        {
259          prop = apr_array_push(props);
260          prop->xmlns = "DAV:";
261          prop->name = "creator-displayname";
262        }
263    }
264  else
265    {
266      /* We found an old subversion server that can't handle
267         the deadprop-count property in the way we expect.
268
269         The neon behavior is to retrieve all properties in this case */
270      prop = apr_array_push(props);
271      prop->xmlns = "DAV:";
272      prop->name = "allprop";
273    }
274
275  prop = apr_array_push(props);
276  prop->xmlns = NULL;
277  prop->name = NULL;
278
279  return (svn_ra_serf__dav_props_t *) props->elts;
280}
281
282/* Implements svn_ra__vtable_t.stat(). */
283svn_error_t *
284svn_ra_serf__stat(svn_ra_session_t *ra_session,
285                  const char *relpath,
286                  svn_revnum_t revision,
287                  svn_dirent_t **dirent,
288                  apr_pool_t *pool)
289{
290  svn_ra_serf__session_t *session = ra_session->priv;
291  svn_error_t *err;
292  struct fill_dirent_baton_t fdb;
293  svn_tristate_t deadprop_count = svn_tristate_unknown;
294  svn_ra_serf__handler_t *handler;
295  const char *url;
296
297  url = session->session_url.path;
298
299  /* If we have a relative path, append it. */
300  if (relpath)
301    url = svn_path_url_add_component2(url, relpath, pool);
302
303  /* If we were given a specific revision, get a URL that refers to that
304     specific revision (rather than floating with HEAD).  */
305  if (SVN_IS_VALID_REVNUM(revision))
306    {
307      SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
308                                          session,
309                                          url, revision,
310                                          pool, pool));
311    }
312
313  fdb.entry = svn_dirent_create(pool);
314  fdb.supports_deadprop_count = &deadprop_count;
315  fdb.result_pool = pool;
316
317  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url,
318                                               SVN_INVALID_REVNUM, "0",
319                                               get_dirent_props(SVN_DIRENT_ALL,
320                                                                session,
321                                                                pool),
322                                               fill_dirent_propfunc, &fdb, pool));
323
324  err = svn_ra_serf__context_run_one(handler, pool);
325
326  if (err)
327    {
328      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
329        {
330          svn_error_clear(err);
331          *dirent = NULL;
332          return SVN_NO_ERROR;
333        }
334      else
335        return svn_error_trace(err);
336    }
337
338  if (deadprop_count == svn_tristate_false
339      && session->supports_deadprop_count == svn_tristate_unknown
340      && !fdb.entry->has_props)
341    {
342      /* We have to requery as the server didn't give us the right
343         information */
344      session->supports_deadprop_count = svn_tristate_false;
345
346      /* Run the same handler again */
347      SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
348    }
349
350  if (deadprop_count != svn_tristate_unknown)
351    session->supports_deadprop_count = deadprop_count;
352
353  *dirent = fdb.entry;
354
355  return SVN_NO_ERROR;
356}
357
358/* Baton for get_dir_dirents_cb and get_dir_props_cb */
359struct get_dir_baton_t
360{
361  apr_pool_t *result_pool;
362  apr_hash_t *dirents;
363  apr_hash_t *ret_props;
364  svn_boolean_t is_directory;
365  svn_tristate_t supports_deadprop_count;
366  const char *path;
367};
368
369/* Implements svn_ra_serf__prop_func_t */
370static svn_error_t *
371get_dir_dirents_cb(void *baton,
372                   const char *path,
373                   const char *ns,
374                   const char *name,
375                   const svn_string_t *value,
376                   apr_pool_t *scratch_pool)
377{
378  struct get_dir_baton_t *db = baton;
379  const char *relpath;
380
381  relpath = svn_fspath__skip_ancestor(db->path, path);
382
383  if (relpath && relpath[0] != '\0')
384    {
385      struct fill_dirent_baton_t fdb;
386
387      relpath = svn_path_uri_decode(relpath, scratch_pool);
388      fdb.entry = svn_hash_gets(db->dirents, relpath);
389
390      if (!fdb.entry)
391        {
392          fdb.entry = svn_dirent_create(db->result_pool);
393          svn_hash_sets(db->dirents,
394                        apr_pstrdup(db->result_pool, relpath),
395                        fdb.entry);
396        }
397
398      fdb.result_pool = db->result_pool;
399      fdb.supports_deadprop_count = &db->supports_deadprop_count;
400      SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool));
401    }
402  else if (relpath && !db->is_directory)
403    {
404      if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
405        {
406          if (strcmp(value->data, "collection") != 0)
407            {
408              /* Tell a lie to exit early */
409              return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
410                                      _("Can't get properties of non-directory"));
411            }
412          else
413            db->is_directory = TRUE;
414        }
415    }
416
417  return SVN_NO_ERROR;
418}
419
420/* Implements svn_ra_serf__prop_func */
421static svn_error_t *
422get_dir_props_cb(void *baton,
423                 const char *path,
424                 const char *ns,
425                 const char *name,
426                 const svn_string_t *value,
427                 apr_pool_t *scratch_pool)
428{
429  struct get_dir_baton_t *db = baton;
430  const char *propname;
431
432  propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool);
433  if (propname)
434    {
435      svn_hash_sets(db->ret_props, propname,
436                    svn_string_dup(value, db->result_pool));
437      return SVN_NO_ERROR;
438    }
439
440  if (!db->is_directory)
441    {
442      if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
443        {
444          if (strcmp(value->data, "collection") != 0)
445            {
446              /* Tell a lie to exit early */
447              return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
448                                      _("Can't get properties of non-directory"));
449            }
450          else
451            db->is_directory = TRUE;
452        }
453    }
454
455  return SVN_NO_ERROR;
456}
457
458/* Implements svn_ra__vtable_t.get_dir(). */
459svn_error_t *
460svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
461                     apr_hash_t **dirents,
462                     svn_revnum_t *fetched_rev,
463                     apr_hash_t **ret_props,
464                     const char *rel_path,
465                     svn_revnum_t revision,
466                     apr_uint32_t dirent_fields,
467                     apr_pool_t *result_pool)
468{
469  svn_ra_serf__session_t *session = ra_session->priv;
470  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
471  svn_ra_serf__handler_t *dirent_handler = NULL;
472  svn_ra_serf__handler_t *props_handler = NULL;
473  const char *path;
474  struct get_dir_baton_t gdb;
475  svn_error_t *err = SVN_NO_ERROR;
476
477  gdb.result_pool = result_pool;
478  gdb.is_directory = FALSE;
479  gdb.supports_deadprop_count = svn_tristate_unknown;
480
481  path = session->session_url.path;
482
483  /* If we have a relative path, URI encode and append it. */
484  if (rel_path)
485    {
486      path = svn_path_url_add_component2(path, rel_path, scratch_pool);
487    }
488
489  /* If the user specified a peg revision other than HEAD, we have to fetch
490     the baseline collection url for that revision. If not, we can use the
491     public url. */
492  if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
493    {
494      SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
495                                          session,
496                                          path, revision,
497                                          scratch_pool, scratch_pool));
498      revision = SVN_INVALID_REVNUM;
499    }
500  /* REVISION is always SVN_INVALID_REVNUM  */
501  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
502
503  gdb.path = path;
504
505  /* If we're asked for children, fetch them now. */
506  if (dirents)
507    {
508      /* Always request node kind to check that path is really a
509       * directory. */
510      if (!ret_props)
511        dirent_fields |= SVN_DIRENT_KIND;
512
513      gdb.dirents = apr_hash_make(result_pool);
514
515      SVN_ERR(svn_ra_serf__create_propfind_handler(
516                                          &dirent_handler, session,
517                                          path, SVN_INVALID_REVNUM, "1",
518                                          get_dirent_props(dirent_fields,
519                                                           session,
520                                                           scratch_pool),
521                                          get_dir_dirents_cb, &gdb,
522                                          scratch_pool));
523
524      svn_ra_serf__request_create(dirent_handler);
525    }
526  else
527    gdb.dirents = NULL;
528
529  if (ret_props)
530    {
531      gdb.ret_props = apr_hash_make(result_pool);
532      SVN_ERR(svn_ra_serf__create_propfind_handler(
533                                          &props_handler, session,
534                                          path, SVN_INVALID_REVNUM, "0",
535                                          all_props,
536                                          get_dir_props_cb, &gdb,
537                                          scratch_pool));
538
539      svn_ra_serf__request_create(props_handler);
540    }
541  else
542    gdb.ret_props = NULL;
543
544  if (dirent_handler)
545    {
546      err = svn_error_trace(
547              svn_ra_serf__context_run_wait(&dirent_handler->done,
548                                            session,
549                                            scratch_pool));
550
551      if (err)
552        {
553          svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */
554          return err;
555        }
556
557      if (gdb.supports_deadprop_count == svn_tristate_false
558          && session->supports_deadprop_count == svn_tristate_unknown
559          && dirent_fields & SVN_DIRENT_HAS_PROPS)
560        {
561          /* We have to requery as the server didn't give us the right
562             information */
563          session->supports_deadprop_count = svn_tristate_false;
564
565          apr_hash_clear(gdb.dirents);
566
567          SVN_ERR(svn_ra_serf__create_propfind_handler(
568                                              &dirent_handler, session,
569                                              path, SVN_INVALID_REVNUM, "1",
570                                              get_dirent_props(dirent_fields,
571                                                               session,
572                                                               scratch_pool),
573                                              get_dir_dirents_cb, &gdb,
574                                              scratch_pool));
575
576          svn_ra_serf__request_create(dirent_handler);
577        }
578    }
579
580  if (props_handler)
581    {
582      err = svn_error_trace(
583              svn_ra_serf__context_run_wait(&props_handler->done,
584                                            session,
585                                            scratch_pool));
586    }
587
588  /* And dirent again for the case when we had to send the request again */
589  if (! err && dirent_handler)
590    {
591      err = svn_error_trace(
592              svn_ra_serf__context_run_wait(&dirent_handler->done,
593                                            session,
594                                            scratch_pool));
595    }
596
597  if (!err && gdb.supports_deadprop_count != svn_tristate_unknown)
598    session->supports_deadprop_count = gdb.supports_deadprop_count;
599
600  svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */
601
602  SVN_ERR(err);
603
604  if (!gdb.is_directory)
605    return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
606                            _("Can't get entries of non-directory"));
607
608  if (ret_props)
609    *ret_props = gdb.ret_props;
610
611  if (dirents)
612    *dirents = gdb.dirents;
613
614  return SVN_NO_ERROR;
615}
616