svn-3673

Version:

1.7.0

Bug Link:

http://subversion.tigris.org/issues/show_bug.cgi?id=3673

Patch Link:

source code patch: http://svn.apache.org/viewvc?diff_format=h&view=revision&revision=961397

reproduction script:

http://svn.haxx.se/dev/archive-2010-07/att-0151/REPOSITORY.root.sh

Symptom:

‘svn move’ causes ‘REPOSITORY.root may not be null’ error.

Moving a directory which contains a moved file triggers an error.

To be specific, assume that there is a directory A and a file f in A.

First, we rename the file ‘A/f’  by ‘svn move’, and then rename the directory ‘A’, then it introduces the error message.

FYI, if we inverse two operations, then we can avoid the error.

How it is diagnosed:

reproduced by running  reproduction script:

*Result: Two error messages are printed.

svn: constraint failed

svn: REPOSITORY.root may not be NULL

Error message creation point: "REPOSITORY.root may not be NULL"

SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(...){

  ... ...

  for(i=0; i<nCol; i++){

    ...

    onError = pTab->aCol[i].notNull;

    ...

    switch( onError ){

      case OE_Abort:

        sqlite3MayAbort(pParse);

      case OE_Rollback:

      case OE_Fail: {

        char *zMsg;

        j1 = sqlite3VdbeAddOp3(v, OP_HaltIfNull,

                                  SQLITE_CONSTRAINT, onError, regData+i);

/* pTab->zName == “REPOSITORY” and pTab->aCol[i].zName == “root” */

        zMsg = sqlite3MPrintf(pParse->db, "%s.%s may not be NULL",

                              pTab->zName, pTab->aCol[i].zName);

        sqlite3VdbeChangeP4(v, -1, zMsg, P4_DYNAMIC);

        break;

      }

      …

call stack:

#0  sqlite3GenerateConstraintChecks (...) at /.../sqlite3.c:83459

/* i guess sqlite3 tried to insert some data but it failed */

#1  sqlite3Insert (..., onError=99) at /.../sqlite3.c:83240

...

#8  svn_sqlite__prepare (..., text=0x2aaaaadcaaf0 "insert into repository (root, uuid) values (?1, ?2); ") at subversion/libsvn_subr/sqlite.c:162

#9  svn_sqlite__get_statement (...) at subversion/libsvn_subr/sqlite.c:142

/* repos_root_url and repos_uuid equals 0x0. It looks strange. */

#10 create_repos_id (repos_root_url=0x0, repos_uuid=0x0, ...) at subversion/libsvn_wc/wc_db.c:602

#11 get_info_for_copy (local_abspath=0x6cddc0 "/home/yyzhou/mmlee/LOG-project/svn/3673/alpha/A/f", ...) at subversion/libsvn_wc/wc_db.c:2854

/* need to move “A/f” to “B/f” */

#12 svn_wc__db_op_copy (src_abspath=0x6cddc0 "/home/yyzhou/mmlee/LOG-project/svn/3673/alpha/A/f", dst_abspath=0x6cddf8 "/home/yyzhou/mmlee/LOG-project/svn/3673/alpha/B/f", ...) at subversion/libsvn_wc/wc_db.c:2932

#23 svn_cl__move (...) at subversion/svn/move-cmd.c:92

#24 main (argc=4, argv=0x7fffffffe7e8) at subversion/svn/main.c:2312

When it tries to move the file ‘f’ in the directory ‘A’ to the directory ‘B’, ‘contraint failed’ message is printed. In ‘get_info_for_copy’ function, call ‘create_repos_id with the arguement ‘repos_root_url’ and ‘ropos_uuid’.

static svn_error_t *

get_info_for_copy(...)

{

  const char *repos_relpath, *repos_root_url, *repos_uuid;

  svn_revnum_t revision;

/* local_abspath “/home/yyzhou/mmlee/LOG-project/svn/3673/alpha/A/f */

/* svn_wc__db_read_info seems to get the information of repository from DB */

  SVN_ERR(svn_wc__db_read_info(status, &revision, &repos_relpath,

                               &repos_root_url, &repos_uuid,

                               db, local_abspath, ...));

  if (*status == svn_wc__db_status_excluded)

    {

      ...

    }

  else if (*status != svn_wc__db_status_added)

    {

      *copyfrom_relpath = repos_relpath;

      *copyfrom_rev = revision;

/* pass the information from ‘svn_wc__db_read_info’ */

/* In the abnormal case, copyfrom_id = 0, repos_root_url = 0x0, and repos_uuid = 0x0 */

/* these variables are not expected to be 0 */

      SVN_ERR(create_repos_id(copyfrom_id,

                              repos_root_url, repos_uuid, ...));

    }

    ...

callstack:

#0  get_info_for_copy (local_abspath=0x6cddc0 "/home/yyzhou/mmlee/LOG-project/svn/3673/alpha/A/f", ...) at subversion/libsvn_wc/wc_db.c:2809

#1  svn_wc__db_op_copy (src_abspath=0x6cddc0 "/home/yyzhou/mmlee/LOG-project/svn/3673/alpha/A/f", dst_abspath=0x6cddf8 "/home/yyzhou/mmlee/LOG-project/svn/3673/alpha/B/f", ...) at subversion/libsvn_wc/wc_db.c:2932

...

#12 svn_cl__move (...) at subversion/svn/move-cmd.c:92

#13 main (argc=4, argv=0x7fffffffe7e8) at subversion/svn/main.c:2312

In ‘create_repos_id’ function, it returns the existing REPOS_ID value for a given REPOS_ROOT__URL/REPOS_UUID pair. If one does not exist, create a new one.

static svn_error_t *

create_repos_id(apr_int64_t *repos_id,

                const char *repos_root_url,

                const char *repos_uuid,

                ...)

{

  svn_sqlite__stmt_t *get_stmt;

  svn_sqlite__stmt_t *insert_stmt;

  svn_boolean_t have_row;

  SVN_ERR(svn_sqlite__get_statement(&get_stmt, sdb, STMT_SELECT_REPOSITORY));

  SVN_ERR(svn_sqlite__bindf(get_stmt, "s", repos_root_url));

  SVN_ERR(svn_sqlite__step(&have_row, get_stmt));

  ...

  SVN_ERR(svn_sqlite__reset(get_stmt));

  SVN_ERR(svn_sqlite__get_statement(&insert_stmt, sdb,

                                    STMT_INSERT_REPOSITORY));

  SVN_ERR(svn_sqlite__bindf(insert_stmt, "ss", repos_root_url, repos_uuid));

  return svn_error_return(svn_sqlite__insert(repos_id, insert_stmt));

}

In  ‘svn_error_return(svn_sqlite__insert(repos_id, insert_stmt))’ :

svn_error_t *

svn_sqlite__insert(apr_int64_t *row_id, svn_sqlite__stmt_t *stmt)

{

  svn_boolean_t got_row;

  SVN_ERR(svn_sqlite__step(&got_row, stmt));

  if (row_id)

    *row_id = sqlite3_last_insert_rowid(stmt->db->db3);

  return svn_error_return(svn_sqlite__reset(stmt));

}

Calls svn_sqlite__step :

Error message creation point: "contraint failed"

svn_error_t *

svn_sqlite__step(svn_boolean_t *got_row, svn_sqlite__stmt_t *stmt)

{

  int sqlite_result = sqlite3_step(stmt->s3stmt);

  if (sqlite_result != SQLITE_DONE && sqlite_result != SQLITE_ROW)

    {

      svn_error_t *err1, *err2;

/* sqlite_result is abnormal. it returns ‘contraint failed’ error message */

      err1 = svn_error_create(SQLITE_ERROR_CODE(sqlite_result), NULL,

                              sqlite3_errmsg(stmt->db->db3));

      err2 = svn_sqlite__reset(stmt);

      return svn_error_compose_create(err1, err2);

    }

  *got_row = (sqlite_result == SQLITE_ROW);

  stmt->needs_reset = TRUE;

  return SVN_NO_ERROR;

}

Root Cause:

Brief:

‘svn move’ leads to the change of sqlite3 database. The moved file information is not updated so that database access failed and ‘constraint failed’ message comes out.

Detail:

The condition that repos_root_url == 0x0 or repos_uuid == 0x0 looks strange.

It should avoid this situation.

The patch is:

--- subversion/trunk/subversion/libsvn_wc/wc_db.c        2010/07/07 14:49:41        961396

+++ subversion/trunk/subversion/libsvn_wc/wc_db.c        2010/07/07 14:54:18        961397

@@ -2851,6 +2851,10 @@ get_info_for_copy(apr_int64_t *copyfrom_

     {

       *copyfrom_relpath = repos_relpath;

       *copyfrom_rev = revision;

/* repos_root_url == 0x0 / repos_uuid == 0x0 is exceptional case. If so, try to get the appropriate value by calling svn_wc__db_scan_base_repos function */

+      if (!repos_root_url || !repos_uuid)

+        SVN_ERR(svn_wc__db_scan_base_repos(NULL, &repos_root_url, &repos_uuid,

+                                           db, local_abspath,

+                                           scratch_pool, scratch_pool));

       SVN_ERR(create_repos_id(copyfrom_id,

                               repos_root_url, repos_uuid,

                               pdh->wcroot->sdb, scratch_pool));

Failure symptom category

wrong result

Is there any log message?

Yes

How can we automatically insert the log message?

5. log at failed safety check

In wc_db.c:3929

  if (repos_relpath || repos_root_url || repos_uuid)

    {

      const char *base_relpath;

      /* ### unwrap this. we can optimize away the parse_local_abspath.  */

      SVN_ERR(svn_wc__db_scan_base_repos(&base_relpath, repos_root_url,

                                         repos_uuid, db, current_abspath,

                                         result_pool, scratch_pool));

      if (repos_relpath)

        *repos_relpath = svn_dirent_join(base_relpath, build_relpath,

                                         result_pool);

    }

We can learn this condition..