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#include <apr_thread_mutex.h> 31 32#include "svn_fs.h" 33#include "svn_delta.h" 34#include "svn_version.h" 35#include "svn_pools.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 140 141 142/* This function is provided for Subversion 1.0.x compatibility. It 143 has no effect for fsx backed Subversion filesystems. It conforms 144 to the fs_library_vtable_t.bdb_set_errcall() API. */ 145static svn_error_t * 146x_set_errcall(svn_fs_t *fs, 147 void (*db_errcall_fcn)(const char *errpfx, char *msg)) 148{ 149 150 return SVN_NO_ERROR; 151} 152 153typedef struct x_freeze_baton_t { 154 svn_fs_t *fs; 155 svn_fs_freeze_func_t freeze_func; 156 void *freeze_baton; 157} x_freeze_baton_t; 158 159static svn_error_t * 160x_freeze_body(void *baton, 161 apr_pool_t *scratch_pool) 162{ 163 x_freeze_baton_t *b = baton; 164 svn_boolean_t exists; 165 166 SVN_ERR(svn_fs_x__exists_rep_cache(&exists, b->fs, scratch_pool)); 167 if (exists) 168 SVN_ERR(svn_fs_x__with_rep_cache_lock(b->fs, 169 b->freeze_func, b->freeze_baton, 170 scratch_pool)); 171 else 172 SVN_ERR(b->freeze_func(b->freeze_baton, scratch_pool)); 173 174 return SVN_NO_ERROR; 175} 176 177static svn_error_t * 178x_freeze_body2(void *baton, 179 apr_pool_t *scratch_pool) 180{ 181 x_freeze_baton_t *b = baton; 182 SVN_ERR(svn_fs_x__with_write_lock(b->fs, x_freeze_body, baton, 183 scratch_pool)); 184 185 return SVN_NO_ERROR; 186} 187 188static svn_error_t * 189x_freeze(svn_fs_t *fs, 190 svn_fs_freeze_func_t freeze_func, 191 void *freeze_baton, 192 apr_pool_t *scratch_pool) 193{ 194 x_freeze_baton_t b; 195 196 b.fs = fs; 197 b.freeze_func = freeze_func; 198 b.freeze_baton = freeze_baton; 199 200 SVN_ERR(svn_fs__check_fs(fs, TRUE)); 201 SVN_ERR(svn_fs_x__with_pack_lock(fs, x_freeze_body2, &b, scratch_pool)); 202 203 return SVN_NO_ERROR; 204} 205 206static svn_error_t * 207x_info(const void **fsx_info, 208 svn_fs_t *fs, 209 apr_pool_t *result_pool, 210 apr_pool_t *scratch_pool) 211{ 212 svn_fs_x__data_t *ffd = fs->fsap_data; 213 svn_fs_fsx_info_t *info = apr_palloc(result_pool, sizeof(*info)); 214 info->fs_type = SVN_FS_TYPE_FSX; 215 info->shard_size = ffd->max_files_per_dir; 216 info->min_unpacked_rev = ffd->min_unpacked_rev; 217 *fsx_info = info; 218 return SVN_NO_ERROR; 219} 220 221/* Wrapper around svn_fs_x__revision_prop() adapting between function 222 signatures. */ 223static svn_error_t * 224x_revision_prop(svn_string_t **value_p, 225 svn_fs_t *fs, 226 svn_revnum_t rev, 227 const char *propname, 228 apr_pool_t *pool) 229{ 230 apr_pool_t *scratch_pool = svn_pool_create(pool); 231 SVN_ERR(svn_fs_x__revision_prop(value_p, fs, rev, propname, pool, 232 scratch_pool)); 233 svn_pool_destroy(scratch_pool); 234 235 return SVN_NO_ERROR; 236} 237 238/* Wrapper around svn_fs_x__get_revision_proplist() adapting between function 239 signatures. */ 240static svn_error_t * 241x_revision_proplist(apr_hash_t **proplist_p, 242 svn_fs_t *fs, 243 svn_revnum_t rev, 244 apr_pool_t *pool) 245{ 246 apr_pool_t *scratch_pool = svn_pool_create(pool); 247 248 /* No need to bypass the caches for r/o access to revprops. */ 249 SVN_ERR(svn_fs_x__get_revision_proplist(proplist_p, fs, rev, FALSE, 250 pool, scratch_pool)); 251 svn_pool_destroy(scratch_pool); 252 253 return SVN_NO_ERROR; 254} 255 256/* Wrapper around svn_fs_x__set_uuid() adapting between function 257 signatures. */ 258static svn_error_t * 259x_set_uuid(svn_fs_t *fs, 260 const char *uuid, 261 apr_pool_t *scratch_pool) 262{ 263 /* Whenever we set a new UUID, imply that FS will also be a different 264 * instance (on formats that support this). */ 265 return svn_error_trace(svn_fs_x__set_uuid(fs, uuid, NULL, scratch_pool)); 266} 267 268/* Wrapper around svn_fs_x__begin_txn() providing the scratch pool. */ 269static svn_error_t * 270x_begin_txn(svn_fs_txn_t **txn_p, 271 svn_fs_t *fs, 272 svn_revnum_t rev, 273 apr_uint32_t flags, 274 apr_pool_t *pool) 275{ 276 apr_pool_t *scratch_pool = svn_pool_create(pool); 277 SVN_ERR(svn_fs_x__begin_txn(txn_p, fs, rev, flags, pool, scratch_pool)); 278 svn_pool_destroy(scratch_pool); 279 280 return SVN_NO_ERROR; 281} 282 283 284 285/* The vtable associated with a specific open filesystem. */ 286static fs_vtable_t fs_vtable = { 287 svn_fs_x__youngest_rev, 288 x_revision_prop, 289 x_revision_proplist, 290 svn_fs_x__change_rev_prop, 291 x_set_uuid, 292 svn_fs_x__revision_root, 293 x_begin_txn, 294 svn_fs_x__open_txn, 295 svn_fs_x__purge_txn, 296 svn_fs_x__list_transactions, 297 svn_fs_x__deltify, 298 svn_fs_x__lock, 299 svn_fs_x__generate_lock_token, 300 svn_fs_x__unlock, 301 svn_fs_x__get_lock, 302 svn_fs_x__get_locks, 303 svn_fs_x__info_format, 304 svn_fs_x__info_config_files, 305 x_info, 306 svn_fs_x__verify_root, 307 x_freeze, 308 x_set_errcall 309}; 310 311 312/* Creating a new filesystem. */ 313 314/* Set up vtable and fsap_data fields in FS. */ 315static svn_error_t * 316initialize_fs_struct(svn_fs_t *fs) 317{ 318 svn_fs_x__data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd)); 319 fs->vtable = &fs_vtable; 320 fs->fsap_data = ffd; 321 return SVN_NO_ERROR; 322} 323 324/* Reset vtable and fsap_data fields in FS such that the FS is basically 325 * closed now. Note that FS must not hold locks when you call this. */ 326static void 327uninitialize_fs_struct(svn_fs_t *fs) 328{ 329 fs->vtable = NULL; 330 fs->fsap_data = NULL; 331} 332 333/* This implements the fs_library_vtable_t.create() API. Create a new 334 fsx-backed Subversion filesystem at path PATH and link it into 335 *FS. 336 337 Perform temporary allocations in SCRATCH_POOL, and fs-global allocations 338 in COMMON_POOL. The latter must be serialized using COMMON_POOL_LOCK. */ 339static svn_error_t * 340x_create(svn_fs_t *fs, 341 const char *path, 342 svn_mutex__t *common_pool_lock, 343 apr_pool_t *scratch_pool, 344 apr_pool_t *common_pool) 345{ 346 SVN_ERR(svn_fs__check_fs(fs, FALSE)); 347 348 SVN_ERR(initialize_fs_struct(fs)); 349 350 SVN_ERR(svn_fs_x__create(fs, path, scratch_pool)); 351 352 SVN_ERR(svn_fs_x__initialize_caches(fs, scratch_pool)); 353 SVN_MUTEX__WITH_LOCK(common_pool_lock, 354 x_serialized_init(fs, common_pool, scratch_pool)); 355 356 return SVN_NO_ERROR; 357} 358 359 360 361/* Gaining access to an existing filesystem. */ 362 363/* This implements the fs_library_vtable_t.open() API. Open an FSX 364 Subversion filesystem located at PATH, set *FS to point to the 365 correct vtable for the filesystem. Use SCRATCH_POOL for any temporary 366 allocations, and COMMON_POOL for fs-global allocations. 367 The latter must be serialized using COMMON_POOL_LOCK. */ 368static svn_error_t * 369x_open(svn_fs_t *fs, 370 const char *path, 371 svn_mutex__t *common_pool_lock, 372 apr_pool_t *scratch_pool, 373 apr_pool_t *common_pool) 374{ 375 apr_pool_t *subpool = svn_pool_create(scratch_pool); 376 377 SVN_ERR(svn_fs__check_fs(fs, FALSE)); 378 379 SVN_ERR(initialize_fs_struct(fs)); 380 381 SVN_ERR(svn_fs_x__open(fs, path, subpool)); 382 383 SVN_ERR(svn_fs_x__initialize_caches(fs, subpool)); 384 SVN_MUTEX__WITH_LOCK(common_pool_lock, 385 x_serialized_init(fs, common_pool, subpool)); 386 387 svn_pool_destroy(subpool); 388 389 return SVN_NO_ERROR; 390} 391 392 393 394/* This implements the fs_library_vtable_t.open_for_recovery() API. */ 395static svn_error_t * 396x_open_for_recovery(svn_fs_t *fs, 397 const char *path, 398 svn_mutex__t *common_pool_lock, 399 apr_pool_t *scratch_pool, 400 apr_pool_t *common_pool) 401{ 402 svn_error_t * err; 403 svn_revnum_t youngest_rev; 404 apr_pool_t * subpool = svn_pool_create(scratch_pool); 405 406 /* Recovery for FSFS is currently limited to recreating the 'current' 407 file from the latest revision. */ 408 409 /* The only thing we have to watch out for is that the 'current' file 410 might not exist or contain garbage. So we'll try to read it here 411 and provide or replace the existing file if we couldn't read it. 412 (We'll also need it to exist later anyway as a source for the new 413 file's permissions). */ 414 415 /* Use a partly-filled fs pointer first to create 'current'. */ 416 fs->path = apr_pstrdup(fs->pool, path); 417 418 SVN_ERR(initialize_fs_struct(fs)); 419 420 /* Figure out the repo format and check that we can even handle it. */ 421 SVN_ERR(svn_fs_x__read_format_file(fs, subpool)); 422 423 /* Now, read 'current' and try to patch it if necessary. */ 424 err = svn_fs_x__youngest_rev(&youngest_rev, fs, subpool); 425 if (err) 426 { 427 const char *file_path; 428 429 /* 'current' file is missing or contains garbage. Since we are trying 430 * to recover from whatever problem there is, being picky about the 431 * error code here won't do us much good. If there is a persistent 432 * problem that we can't fix, it will show up when we try rewrite the 433 * file a few lines further below and we will report the failure back 434 * to the caller. 435 * 436 * Start recovery with HEAD = 0. */ 437 svn_error_clear(err); 438 file_path = svn_fs_x__path_current(fs, subpool); 439 440 /* Best effort to ensure the file exists and is valid. 441 * This may fail for r/o filesystems etc. */ 442 SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool)); 443 SVN_ERR(svn_io_file_create_empty(file_path, subpool)); 444 SVN_ERR(svn_fs_x__write_current(fs, 0, subpool)); 445 } 446 447 uninitialize_fs_struct(fs); 448 svn_pool_destroy(subpool); 449 450 /* Now open the filesystem properly by calling the vtable method directly. */ 451 return x_open(fs, path, common_pool_lock, scratch_pool, common_pool); 452} 453 454 455 456/* This implements the fs_library_vtable_t.upgrade_fs() API. */ 457static svn_error_t * 458x_upgrade(svn_fs_t *fs, 459 const char *path, 460 svn_fs_upgrade_notify_t notify_func, 461 void *notify_baton, 462 svn_cancel_func_t cancel_func, 463 void *cancel_baton, 464 svn_mutex__t *common_pool_lock, 465 apr_pool_t *scratch_pool, 466 apr_pool_t *common_pool) 467{ 468 SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool)); 469 return svn_fs_x__upgrade(fs, notify_func, notify_baton, 470 cancel_func, cancel_baton, scratch_pool); 471} 472 473static svn_error_t * 474x_verify(svn_fs_t *fs, 475 const char *path, 476 svn_revnum_t start, 477 svn_revnum_t end, 478 svn_fs_progress_notify_func_t notify_func, 479 void *notify_baton, 480 svn_cancel_func_t cancel_func, 481 void *cancel_baton, 482 svn_mutex__t *common_pool_lock, 483 apr_pool_t *scratch_pool, 484 apr_pool_t *common_pool) 485{ 486 SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool)); 487 return svn_fs_x__verify(fs, start, end, notify_func, notify_baton, 488 cancel_func, cancel_baton, scratch_pool); 489} 490 491static svn_error_t * 492x_pack(svn_fs_t *fs, 493 const char *path, 494 svn_fs_pack_notify_t notify_func, 495 void *notify_baton, 496 svn_cancel_func_t cancel_func, 497 void *cancel_baton, 498 svn_mutex__t *common_pool_lock, 499 apr_pool_t *scratch_pool, 500 apr_pool_t *common_pool) 501{ 502 SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool)); 503 return svn_fs_x__pack(fs, notify_func, notify_baton, 504 cancel_func, cancel_baton, scratch_pool); 505} 506 507 508 509 510/* This implements the fs_library_vtable_t.hotcopy() API. Copy a 511 possibly live Subversion filesystem SRC_FS from SRC_PATH to a 512 DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to 513 re-copy data which already exists in DST_FS. 514 The CLEAN_LOGS argument is ignored and included for Subversion 515 1.0.x compatibility. The NOTIFY_FUNC and NOTIFY_BATON arguments 516 are also currently ignored. 517 Perform all temporary allocations in SCRATCH_POOL. */ 518static svn_error_t * 519x_hotcopy(svn_fs_t *src_fs, 520 svn_fs_t *dst_fs, 521 const char *src_path, 522 const char *dst_path, 523 svn_boolean_t clean_logs, 524 svn_boolean_t incremental, 525 svn_fs_hotcopy_notify_t notify_func, 526 void *notify_baton, 527 svn_cancel_func_t cancel_func, 528 void *cancel_baton, 529 svn_mutex__t *common_pool_lock, 530 apr_pool_t *scratch_pool, 531 apr_pool_t *common_pool) 532{ 533 /* Open the source repo as usual. */ 534 SVN_ERR(x_open(src_fs, src_path, common_pool_lock, scratch_pool, 535 common_pool)); 536 if (cancel_func) 537 SVN_ERR(cancel_func(cancel_baton)); 538 539 /* Test target repo when in INCREMENTAL mode, initialize it when not. 540 * For this, we need our FS internal data structures to be temporarily 541 * available. */ 542 SVN_ERR(initialize_fs_struct(dst_fs)); 543 SVN_ERR(svn_fs_x__hotcopy_prepare_target(src_fs, dst_fs, dst_path, 544 incremental, scratch_pool)); 545 uninitialize_fs_struct(dst_fs); 546 547 /* Now, the destination repo should open just fine. */ 548 SVN_ERR(x_open(dst_fs, dst_path, common_pool_lock, scratch_pool, 549 common_pool)); 550 if (cancel_func) 551 SVN_ERR(cancel_func(cancel_baton)); 552 553 /* Now, we may copy data as needed ... */ 554 return svn_fs_x__hotcopy(src_fs, dst_fs, incremental, 555 notify_func, notify_baton, 556 cancel_func, cancel_baton, scratch_pool); 557} 558 559 560 561/* This function is included for Subversion 1.0.x compatibility. It 562 has no effect for fsx backed Subversion filesystems. It conforms 563 to the fs_library_vtable_t.bdb_logfiles() API. */ 564static svn_error_t * 565x_logfiles(apr_array_header_t **logfiles, 566 const char *path, 567 svn_boolean_t only_unused, 568 apr_pool_t *pool) 569{ 570 /* A no-op for FSX. */ 571 *logfiles = apr_array_make(pool, 0, sizeof(const char *)); 572 573 return SVN_NO_ERROR; 574} 575 576 577 578 579 580/* Delete the filesystem located at path PATH. Perform any temporary 581 allocations in SCRATCH_POOL. */ 582static svn_error_t * 583x_delete_fs(const char *path, 584 apr_pool_t *scratch_pool) 585{ 586 /* Remove everything. */ 587 return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL, 588 scratch_pool)); 589} 590 591static const svn_version_t * 592x_version(void) 593{ 594 SVN_VERSION_BODY; 595} 596 597static const char * 598x_get_description(void) 599{ 600 return _("Module for working with an experimental (FSX) repository."); 601} 602 603static svn_error_t * 604x_set_svn_fs_open(svn_fs_t *fs, 605 svn_error_t *(*svn_fs_open_)(svn_fs_t **, 606 const char *, 607 apr_hash_t *, 608 apr_pool_t *, 609 apr_pool_t *)) 610{ 611 svn_fs_x__data_t *ffd = fs->fsap_data; 612 ffd->svn_fs_open_ = svn_fs_open_; 613 return SVN_NO_ERROR; 614} 615 616static void * 617x_info_dup(const void *fsx_info_void, 618 apr_pool_t *result_pool) 619{ 620 /* All fields are either ints or static strings. */ 621 const svn_fs_fsx_info_t *fsx_info = fsx_info_void; 622 return apr_pmemdup(result_pool, fsx_info, sizeof(*fsx_info)); 623} 624 625 626/* Base FS library vtable, used by the FS loader library. */ 627 628static fs_library_vtable_t library_vtable = { 629 x_version, 630 x_create, 631 x_open, 632 x_open_for_recovery, 633 x_upgrade, 634 x_verify, 635 x_delete_fs, 636 x_hotcopy, 637 x_get_description, 638 svn_fs_x__recover, 639 x_pack, 640 x_logfiles, 641 NULL /* parse_id */, 642 x_set_svn_fs_open, 643 x_info_dup 644}; 645 646svn_error_t * 647svn_fs_x__init(const svn_version_t *loader_version, 648 fs_library_vtable_t **vtable, 649 apr_pool_t* common_pool) 650{ 651 static const svn_version_checklist_t checklist[] = 652 { 653 { "svn_subr", svn_subr_version }, 654 { "svn_delta", svn_delta_version }, 655 { "svn_fs_util", svn_fs_util__version }, 656 { NULL, NULL } 657 }; 658 659 /* Simplified version check to make sure we can safely use the 660 VTABLE parameter. The FS loader does a more exhaustive check. */ 661 if (loader_version->major != SVN_VER_MAJOR) 662 return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, 663 _("Unsupported FS loader version (%d) for fsx"), 664 loader_version->major); 665 SVN_ERR(svn_ver_check_list2(x_version(), checklist, svn_ver_equal)); 666 667 *vtable = &library_vtable; 668 return SVN_NO_ERROR; 669} 670