fs.c revision 362181
1/* fs.c --- creating, opening and closing filesystems
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23#include <stdlib.h>
24#include <stdio.h>
25#include <string.h>
26
27#include <apr_general.h>
28#include <apr_pools.h>
29#include <apr_file_io.h>
30
31#include "svn_fs.h"
32#include "svn_delta.h"
33#include "svn_version.h"
34#include "svn_pools.h"
35#include "batch_fsync.h"
36#include "fs.h"
37#include "fs_x.h"
38#include "pack.h"
39#include "recovery.h"
40#include "hotcopy.h"
41#include "verify.h"
42#include "tree.h"
43#include "lock.h"
44#include "id.h"
45#include "revprops.h"
46#include "rep-cache.h"
47#include "transaction.h"
48#include "util.h"
49#include "svn_private_config.h"
50#include "private/svn_fs_util.h"
51
52#include "../libsvn_fs/fs-loader.h"
53
54/* A prefix for the pool userdata variables used to hold
55   per-filesystem shared data.  See fs_serialized_init. */
56#define SVN_FSX_SHARED_USERDATA_PREFIX "svn-fsx-shared-"
57
58
59
60/* Initialize the part of FS that requires global serialization across all
61   instances.  The caller is responsible of ensuring that serialization.
62   Use COMMON_POOL for process-wide and SCRATCH_POOL for temporary
63   allocations. */
64static svn_error_t *
65x_serialized_init(svn_fs_t *fs,
66                  apr_pool_t *common_pool,
67                  apr_pool_t *scratch_pool)
68{
69  svn_fs_x__data_t *ffd = fs->fsap_data;
70  const char *key;
71  void *val;
72  svn_fs_x__shared_data_t *ffsd;
73  apr_status_t status;
74
75  /* Note that we are allocating a small amount of long-lived data for
76     each separate repository opened during the lifetime of the
77     svn_fs_initialize pool.  It's unlikely that anyone will notice
78     the modest expenditure; the alternative is to allocate each structure
79     in a subpool, add a reference-count, and add a serialized destructor
80     to the FS vtable.  That's more machinery than it's worth.
81
82     Picking an appropriate key for the shared data is tricky, because,
83     unfortunately, a filesystem UUID is not really unique.  It is implicitly
84     shared between hotcopied (1), dump / loaded (2) or naively copied (3)
85     filesystems.  We tackle this problem by using a combination of the UUID
86     and an instance ID as the key.  This allows us to avoid key clashing
87     in (1) and (2).
88
89     Speaking of (3), there is not so much we can do about it, except maybe
90     provide a convenient way of fixing things.  Naively copied filesystems
91     have identical filesystem UUIDs *and* instance IDs.  With the key being
92     a combination of these two, clashes can be fixed by changing either of
93     them (or both), e.g. with svn_fs_set_uuid(). */
94
95
96  SVN_ERR_ASSERT(fs->uuid);
97  SVN_ERR_ASSERT(ffd->instance_id);
98
99  key = apr_pstrcat(scratch_pool, SVN_FSX_SHARED_USERDATA_PREFIX,
100                    fs->uuid, ":", ffd->instance_id, SVN_VA_NULL);
101  status = apr_pool_userdata_get(&val, key, common_pool);
102  if (status)
103    return svn_error_wrap_apr(status, _("Can't fetch FSX shared data"));
104  ffsd = val;
105
106  if (!ffsd)
107    {
108      ffsd = apr_pcalloc(common_pool, sizeof(*ffsd));
109      ffsd->common_pool = common_pool;
110
111      /* POSIX fcntl locks are per-process, so we need a mutex for
112         intra-process synchronization when grabbing the repository write
113         lock. */
114      SVN_ERR(svn_mutex__init(&ffsd->fs_write_lock,
115                              SVN_FS_X__USE_LOCK_MUTEX, common_pool));
116
117      /* ... the pack lock ... */
118      SVN_ERR(svn_mutex__init(&ffsd->fs_pack_lock,
119                              SVN_FS_X__USE_LOCK_MUTEX, common_pool));
120
121      /* ... not to mention locking the txn-current file. */
122      SVN_ERR(svn_mutex__init(&ffsd->txn_current_lock,
123                              SVN_FS_X__USE_LOCK_MUTEX, common_pool));
124
125      /* We also need a mutex for synchronizing access to the active
126         transaction list and free transaction pointer. */
127      SVN_ERR(svn_mutex__init(&ffsd->txn_list_lock, TRUE, common_pool));
128
129      key = apr_pstrdup(common_pool, key);
130      status = apr_pool_userdata_set(ffsd, key, NULL, common_pool);
131      if (status)
132        return svn_error_wrap_apr(status, _("Can't store FSX shared data"));
133    }
134
135  ffd->shared = ffsd;
136
137  return SVN_NO_ERROR;
138}
139
140svn_error_t *
141svn_fs_x__initialize_shared_data(svn_fs_t *fs,
142                                 svn_mutex__t *common_pool_lock,
143                                 apr_pool_t *scratch_pool,
144                                 apr_pool_t *common_pool)
145{
146  SVN_MUTEX__WITH_LOCK(common_pool_lock,
147                       x_serialized_init(fs, common_pool, scratch_pool));
148
149  return SVN_NO_ERROR;
150}
151
152
153
154/* This function is provided for Subversion 1.0.x compatibility.  It
155   has no effect for fsx backed Subversion filesystems.  It conforms
156   to the fs_library_vtable_t.bdb_set_errcall() API. */
157static svn_error_t *
158x_set_errcall(svn_fs_t *fs,
159              void (*db_errcall_fcn)(const char *errpfx, char *msg))
160{
161
162  return SVN_NO_ERROR;
163}
164
165typedef struct x_freeze_baton_t {
166  svn_fs_t *fs;
167  svn_fs_freeze_func_t freeze_func;
168  void *freeze_baton;
169} x_freeze_baton_t;
170
171static svn_error_t *
172x_freeze_body(void *baton,
173              apr_pool_t *scratch_pool)
174{
175  x_freeze_baton_t *b = baton;
176  svn_boolean_t exists;
177
178  SVN_ERR(svn_fs_x__exists_rep_cache(&exists, b->fs, scratch_pool));
179  if (exists)
180    SVN_ERR(svn_fs_x__with_rep_cache_lock(b->fs,
181                                          b->freeze_func, b->freeze_baton,
182                                          scratch_pool));
183  else
184    SVN_ERR(b->freeze_func(b->freeze_baton, scratch_pool));
185
186  return SVN_NO_ERROR;
187}
188
189static svn_error_t *
190x_freeze_body2(void *baton,
191               apr_pool_t *scratch_pool)
192{
193  x_freeze_baton_t *b = baton;
194  SVN_ERR(svn_fs_x__with_write_lock(b->fs, x_freeze_body, baton,
195                                    scratch_pool));
196
197  return SVN_NO_ERROR;
198}
199
200static svn_error_t *
201x_freeze(svn_fs_t *fs,
202         svn_fs_freeze_func_t freeze_func,
203         void *freeze_baton,
204         apr_pool_t *scratch_pool)
205{
206  x_freeze_baton_t b;
207
208  b.fs = fs;
209  b.freeze_func = freeze_func;
210  b.freeze_baton = freeze_baton;
211
212  SVN_ERR(svn_fs__check_fs(fs, TRUE));
213  SVN_ERR(svn_fs_x__with_pack_lock(fs, x_freeze_body2, &b, scratch_pool));
214
215  return SVN_NO_ERROR;
216}
217
218static svn_error_t *
219x_info(const void **fsx_info,
220       svn_fs_t *fs,
221       apr_pool_t *result_pool,
222       apr_pool_t *scratch_pool)
223{
224  svn_fs_x__data_t *ffd = fs->fsap_data;
225  svn_fs_fsx_info_t *info = apr_palloc(result_pool, sizeof(*info));
226  info->fs_type = SVN_FS_TYPE_FSX;
227  info->shard_size = ffd->max_files_per_dir;
228  info->min_unpacked_rev = ffd->min_unpacked_rev;
229  *fsx_info = info;
230  return SVN_NO_ERROR;
231}
232
233static svn_error_t *
234x_refresh_revprops(svn_fs_t *fs,
235                   apr_pool_t *scratch_pool)
236{
237  svn_fs_x__invalidate_revprop_generation(fs);
238  return SVN_NO_ERROR;
239}
240
241/* Wrapper around svn_fs_x__get_revision_proplist() adapting between function
242   signatures. */
243static svn_error_t *
244x_revision_proplist(apr_hash_t **proplist_p,
245                    svn_fs_t *fs,
246                    svn_revnum_t rev,
247                    svn_boolean_t refresh,
248                    apr_pool_t *result_pool,
249                    apr_pool_t *scratch_pool)
250{
251  /* No need to bypass the caches for r/o access to revprops. */
252  SVN_ERR(svn_fs_x__get_revision_proplist(proplist_p, fs, rev, FALSE,
253                                          refresh, result_pool,
254                                          scratch_pool));
255
256  return SVN_NO_ERROR;
257}
258
259/* Wrapper around svn_fs_x__set_uuid() adapting between function
260   signatures. */
261static svn_error_t *
262x_set_uuid(svn_fs_t *fs,
263           const char *uuid,
264           apr_pool_t *scratch_pool)
265{
266  /* Whenever we set a new UUID, imply that FS will also be a different
267   * instance (on formats that support this). */
268  return svn_error_trace(svn_fs_x__set_uuid(fs, uuid, NULL, TRUE,
269                                            scratch_pool));
270}
271
272/* Wrapper around svn_fs_x__begin_txn() providing the scratch pool. */
273static svn_error_t *
274x_begin_txn(svn_fs_txn_t **txn_p,
275            svn_fs_t *fs,
276            svn_revnum_t rev,
277            apr_uint32_t flags,
278            apr_pool_t *pool)
279{
280  apr_pool_t *scratch_pool = svn_pool_create(pool);
281  SVN_ERR(svn_fs_x__begin_txn(txn_p, fs, rev, flags, pool, scratch_pool));
282  svn_pool_destroy(scratch_pool);
283
284  return SVN_NO_ERROR;
285}
286
287
288
289/* The vtable associated with a specific open filesystem. */
290static fs_vtable_t fs_vtable = {
291  svn_fs_x__youngest_rev,
292  x_refresh_revprops,
293  svn_fs_x__revision_prop,
294  x_revision_proplist,
295  svn_fs_x__change_rev_prop,
296  x_set_uuid,
297  svn_fs_x__revision_root,
298  x_begin_txn,
299  svn_fs_x__open_txn,
300  svn_fs_x__purge_txn,
301  svn_fs_x__list_transactions,
302  svn_fs_x__deltify,
303  svn_fs_x__lock,
304  svn_fs_x__generate_lock_token,
305  svn_fs_x__unlock,
306  svn_fs_x__get_lock,
307  svn_fs_x__get_locks,
308  svn_fs_x__info_format,
309  svn_fs_x__info_config_files,
310  x_info,
311  svn_fs_x__verify_root,
312  x_freeze,
313  x_set_errcall,
314  NULL /* ioctl */
315};
316
317
318/* Creating a new filesystem. */
319
320/* Set up vtable and fsap_data fields in FS. */
321static svn_error_t *
322initialize_fs_struct(svn_fs_t *fs)
323{
324  svn_fs_x__data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
325  ffd->revprop_generation = -1;
326  ffd->flush_to_disk = TRUE;
327
328  fs->vtable = &fs_vtable;
329  fs->fsap_data = ffd;
330  return SVN_NO_ERROR;
331}
332
333/* Reset vtable and fsap_data fields in FS such that the FS is basically
334 * closed now.  Note that FS must not hold locks when you call this. */
335static void
336uninitialize_fs_struct(svn_fs_t *fs)
337{
338  fs->vtable = NULL;
339  fs->fsap_data = NULL;
340}
341
342/* This implements the fs_library_vtable_t.create() API.  Create a new
343   fsx-backed Subversion filesystem at path PATH and link it into
344   *FS.
345
346   Perform temporary allocations in SCRATCH_POOL, and fs-global allocations
347   in COMMON_POOL.  The latter must be serialized using COMMON_POOL_LOCK. */
348static svn_error_t *
349x_create(svn_fs_t *fs,
350         const char *path,
351         svn_mutex__t *common_pool_lock,
352         apr_pool_t *scratch_pool,
353         apr_pool_t *common_pool)
354{
355  SVN_ERR(svn_fs__check_fs(fs, FALSE));
356
357  SVN_ERR(initialize_fs_struct(fs));
358
359  SVN_ERR(svn_fs_x__create(fs, path, scratch_pool));
360
361  SVN_ERR(svn_fs_x__initialize_caches(fs, scratch_pool));
362  SVN_MUTEX__WITH_LOCK(common_pool_lock,
363                       x_serialized_init(fs, common_pool, scratch_pool));
364
365  return SVN_NO_ERROR;
366}
367
368
369
370/* Gaining access to an existing filesystem.  */
371
372/* This implements the fs_library_vtable_t.open() API.  Open an FSX
373   Subversion filesystem located at PATH, set *FS to point to the
374   correct vtable for the filesystem.  Use SCRATCH_POOL for any temporary
375   allocations, and COMMON_POOL for fs-global allocations.
376   The latter must be serialized using COMMON_POOL_LOCK.  */
377static svn_error_t *
378x_open(svn_fs_t *fs,
379       const char *path,
380       svn_mutex__t *common_pool_lock,
381       apr_pool_t *scratch_pool,
382       apr_pool_t *common_pool)
383{
384  apr_pool_t *subpool = svn_pool_create(scratch_pool);
385
386  SVN_ERR(svn_fs__check_fs(fs, FALSE));
387
388  SVN_ERR(initialize_fs_struct(fs));
389
390  SVN_ERR(svn_fs_x__open(fs, path, subpool));
391
392  SVN_ERR(svn_fs_x__initialize_caches(fs, subpool));
393  SVN_MUTEX__WITH_LOCK(common_pool_lock,
394                       x_serialized_init(fs, common_pool, subpool));
395
396  svn_pool_destroy(subpool);
397
398  return SVN_NO_ERROR;
399}
400
401
402
403/* This implements the fs_library_vtable_t.open_for_recovery() API. */
404static svn_error_t *
405x_open_for_recovery(svn_fs_t *fs,
406                    const char *path,
407                    svn_mutex__t *common_pool_lock,
408                    apr_pool_t *scratch_pool,
409                    apr_pool_t *common_pool)
410{
411  svn_error_t * err;
412  svn_revnum_t youngest_rev;
413  apr_pool_t * subpool = svn_pool_create(scratch_pool);
414
415  /* Recovery for FSFS is currently limited to recreating the 'current'
416     file from the latest revision. */
417
418  /* The only thing we have to watch out for is that the 'current' file
419     might not exist or contain garbage.  So we'll try to read it here
420     and provide or replace the existing file if we couldn't read it.
421     (We'll also need it to exist later anyway as a source for the new
422     file's permissions). */
423
424  /* Use a partly-filled fs pointer first to create 'current'. */
425  fs->path = apr_pstrdup(fs->pool, path);
426
427  SVN_ERR(initialize_fs_struct(fs));
428
429  /* Figure out the repo format and check that we can even handle it. */
430  SVN_ERR(svn_fs_x__read_format_file(fs, subpool));
431
432  /* Now, read 'current' and try to patch it if necessary. */
433  err = svn_fs_x__youngest_rev(&youngest_rev, fs, subpool);
434  if (err)
435    {
436      const char *file_path;
437
438      /* 'current' file is missing or contains garbage.  Since we are trying
439       * to recover from whatever problem there is, being picky about the
440       * error code here won't do us much good.  If there is a persistent
441       * problem that we can't fix, it will show up when we try rewrite the
442       * file a few lines further below and we will report the failure back
443       * to the caller.
444       *
445       * Start recovery with HEAD = 0. */
446      svn_error_clear(err);
447      file_path = svn_fs_x__path_current(fs, subpool);
448
449      /* Best effort to ensure the file exists and is valid.
450       * This may fail for r/o filesystems etc. */
451      SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool));
452      SVN_ERR(svn_io_file_create_empty(file_path, subpool));
453      SVN_ERR(svn_fs_x__write_current(fs, 0, subpool));
454    }
455
456  uninitialize_fs_struct(fs);
457  svn_pool_destroy(subpool);
458
459  /* Now open the filesystem properly by calling the vtable method directly. */
460  return x_open(fs, path, common_pool_lock, scratch_pool, common_pool);
461}
462
463
464
465/* This implements the fs_library_vtable_t.upgrade_fs() API. */
466static svn_error_t *
467x_upgrade(svn_fs_t *fs,
468          const char *path,
469          svn_fs_upgrade_notify_t notify_func,
470          void *notify_baton,
471          svn_cancel_func_t cancel_func,
472          void *cancel_baton,
473          svn_mutex__t *common_pool_lock,
474          apr_pool_t *scratch_pool,
475          apr_pool_t *common_pool)
476{
477  SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
478  return svn_fs_x__upgrade(fs, notify_func, notify_baton,
479                           cancel_func, cancel_baton, scratch_pool);
480}
481
482static svn_error_t *
483x_verify(svn_fs_t *fs,
484         const char *path,
485         svn_revnum_t start,
486         svn_revnum_t end,
487         svn_fs_progress_notify_func_t notify_func,
488         void *notify_baton,
489         svn_cancel_func_t cancel_func,
490         void *cancel_baton,
491         svn_mutex__t *common_pool_lock,
492         apr_pool_t *scratch_pool,
493         apr_pool_t *common_pool)
494{
495  SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
496  return svn_fs_x__verify(fs, start, end, notify_func, notify_baton,
497                          cancel_func, cancel_baton, scratch_pool);
498}
499
500static svn_error_t *
501x_pack(svn_fs_t *fs,
502       const char *path,
503       svn_fs_pack_notify_t notify_func,
504       void *notify_baton,
505       svn_cancel_func_t cancel_func,
506       void *cancel_baton,
507       svn_mutex__t *common_pool_lock,
508       apr_pool_t *scratch_pool,
509       apr_pool_t *common_pool)
510{
511  SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
512  return svn_fs_x__pack(fs, 0, notify_func, notify_baton,
513                        cancel_func, cancel_baton, scratch_pool);
514}
515
516
517
518
519/* This implements the fs_library_vtable_t.hotcopy() API.  Copy a
520   possibly live Subversion filesystem SRC_FS from SRC_PATH to a
521   DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to
522   re-copy data which already exists in DST_FS.
523   The CLEAN_LOGS argument is ignored and included for Subversion
524   1.0.x compatibility.  The NOTIFY_FUNC and NOTIFY_BATON arguments
525   are also currently ignored.
526   Perform all temporary allocations in SCRATCH_POOL. */
527static svn_error_t *
528x_hotcopy(svn_fs_t *src_fs,
529          svn_fs_t *dst_fs,
530          const char *src_path,
531          const char *dst_path,
532          svn_boolean_t clean_logs,
533          svn_boolean_t incremental,
534          svn_fs_hotcopy_notify_t notify_func,
535          void *notify_baton,
536          svn_cancel_func_t cancel_func,
537          void *cancel_baton,
538          svn_mutex__t *common_pool_lock,
539          apr_pool_t *scratch_pool,
540          apr_pool_t *common_pool)
541{
542  /* Open the source repo as usual. */
543  SVN_ERR(x_open(src_fs, src_path, common_pool_lock, scratch_pool,
544                 common_pool));
545  if (cancel_func)
546    SVN_ERR(cancel_func(cancel_baton));
547
548  SVN_ERR(svn_fs__check_fs(dst_fs, FALSE));
549  SVN_ERR(initialize_fs_struct(dst_fs));
550
551  /* In INCREMENTAL mode, svn_fs_x__hotcopy() will open DST_FS.
552     Otherwise, it's not an FS yet --- possibly just an empty dir --- so
553     can't be opened.
554   */
555  return svn_fs_x__hotcopy(src_fs, dst_fs, src_path, dst_path,
556                            incremental, notify_func, notify_baton,
557                            cancel_func, cancel_baton, common_pool_lock,
558                            scratch_pool, common_pool);
559}
560
561
562
563/* This function is included for Subversion 1.0.x compatibility.  It
564   has no effect for fsx backed Subversion filesystems.  It conforms
565   to the fs_library_vtable_t.bdb_logfiles() API. */
566static svn_error_t *
567x_logfiles(apr_array_header_t **logfiles,
568           const char *path,
569           svn_boolean_t only_unused,
570           apr_pool_t *pool)
571{
572  /* A no-op for FSX. */
573  *logfiles = apr_array_make(pool, 0, sizeof(const char *));
574
575  return SVN_NO_ERROR;
576}
577
578
579
580
581
582/* Delete the filesystem located at path PATH.  Perform any temporary
583   allocations in SCRATCH_POOL. */
584static svn_error_t *
585x_delete_fs(const char *path,
586            apr_pool_t *scratch_pool)
587{
588  /* Remove everything. */
589  return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL,
590                                            scratch_pool));
591}
592
593static const svn_version_t *
594x_version(void)
595{
596  SVN_VERSION_BODY;
597}
598
599static const char *
600x_get_description(void)
601{
602  return _("Module for working with an experimental (FSX) repository.");
603}
604
605static svn_error_t *
606x_set_svn_fs_open(svn_fs_t *fs,
607                  svn_error_t *(*svn_fs_open_)(svn_fs_t **,
608                                               const char *,
609                                               apr_hash_t *,
610                                               apr_pool_t *,
611                                               apr_pool_t *))
612{
613  svn_fs_x__data_t *ffd = fs->fsap_data;
614  ffd->svn_fs_open_ = svn_fs_open_;
615  return SVN_NO_ERROR;
616}
617
618static void *
619x_info_dup(const void *fsx_info_void,
620           apr_pool_t *result_pool)
621{
622  /* All fields are either ints or static strings. */
623  const svn_fs_fsx_info_t *fsx_info = fsx_info_void;
624  return apr_pmemdup(result_pool, fsx_info, sizeof(*fsx_info));
625}
626
627
628/* Base FS library vtable, used by the FS loader library. */
629
630static fs_library_vtable_t library_vtable = {
631  x_version,
632  x_create,
633  x_open,
634  x_open_for_recovery,
635  x_upgrade,
636  x_verify,
637  x_delete_fs,
638  x_hotcopy,
639  x_get_description,
640  svn_fs_x__recover,
641  x_pack,
642  x_logfiles,
643  NULL /* parse_id */,
644  x_set_svn_fs_open,
645  x_info_dup,
646  NULL /* ioctl */
647};
648
649svn_error_t *
650svn_fs_x__init(const svn_version_t *loader_version,
651               fs_library_vtable_t **vtable,
652               apr_pool_t* common_pool)
653{
654  static const svn_version_checklist_t checklist[] =
655    {
656      { "svn_subr",  svn_subr_version },
657      { "svn_delta", svn_delta_version },
658      { "svn_fs_util", svn_fs_util__version },
659      { NULL, NULL }
660    };
661
662  /* Simplified version check to make sure we can safely use the
663     VTABLE parameter. The FS loader does a more exhaustive check. */
664  if (loader_version->major != SVN_VER_MAJOR)
665    return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
666                             _("Unsupported FS loader version (%d) for fsx"),
667                             loader_version->major);
668  SVN_ERR(svn_ver_check_list2(x_version(), checklist, svn_ver_equal));
669
670  SVN_ERR(svn_fs_x__batch_fsync_init(common_pool));
671
672  *vtable = &library_vtable;
673  return SVN_NO_ERROR;
674}
675