如何检测sqlite3是否创建了数据库文件?

时间:2016-02-22 13:11:13

标签: c sqlite race-condition tocttou

我正在编写一个使用sqlite3数据库文件来存储其数据的程序。如果我用

打开数据库文件
sqlite3_open_v2(filename, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)

如果数据库文件不存在,则创建该文件。如何在打开数据库文件之前查明数据库文件是否存在? sqlite3 shell使用如下代码:

  /* Go ahead and open the database file if it already exists.  If the
  ** file does not exist, delay opening it.  This prevents empty database
  ** files from being created if a user mistypes the database name argument
  ** to the sqlite command-line tool.
  */
  if( access(data.zDbFilename, 0)==0 ){
    open_db(&data, 0);
  }

此代码在access调用之后和open_db调用之前由另一个进程创建数据库文件时具有竞争条件(检查时间与使用时间)

另一个答案(我现在无法找到)建议检查application_iduser_version字段。如果它们为零,则只创建一个数据库。我研究了这种方法,发现许多应用程序实际上并不打算在新创建的数据库中设置这些字段,所以这种方法充其量是模糊的,我不认为它解决了我的问题。

有没有一种方法可以确定数据库在打开之前是否存在,而不涉及这样的竞争条件?如果我只能通过sqlite3查明数据库文件是否已初始化(例如,截断文件中填充了sqlite3标题),也是可以接受的。

拥有这样一个例程的目的是能够找出我是否需要在数据库中创建我需要的所有表。我不想意外地覆盖另一个应用程序放置在那里的另一个文件。

示例代码

可以在以下bash脚本中找到该问题的简化说明。它模拟两个应用程序。他们都以类似的方式工作:

  1. 创建或打开数据库test.db
  2. 如果之前不存在数据库,请创建一个表test并在其中写入一行。第一个应用程序的值为1,第二个应用程序的值为2。
  3. 打印数据库的内容。
  4. 以下是代码:

    #!/bin/sh
    
    # sleeps are inserted to make the race conditions easier to trigger
    
    application() {
        echo Checking if database exists...
        [ ! -f test.db ]
        status=$?
        sleep 2
    
        if (exit $status)
        then
            echo Database not found, making tables
            sqlite3 test.db "CREATE TABLE IF NOT EXISTS test (a);"
            sleep 2
            echo Writing initial record into database
            sqlite3 test.db "INSERT INTO test VALUES ($1);"
            sleep 2
        else
            echo Database found, checking if it belongs to me
        fi
    
        echo Printing content of database
        sqlite3 test.db "SELECT * FROM test;"
    }
    
    rm -f test.db
    echo First test: app1 and app1 race
    application 1 & (sleep 1 ; application 1)
    
    rm -f test.db
    echo Second test: app2 and app1 race
    application 2 & (sleep 1 ; application 1)
    

    我的目标是确保以下情况永远不会发生:

    • 应用程序实例打开数据库文件,得出结论:它没有初始化并初始化它(通过创建表并写入初始记录),即使它已经包含来自同一应用程序的不同实例的数据或者来自不同的应用程序。
    • 写入属于不同应用程序的数据库文件。

    如果application编程正确,则每次运行只会初始化数据库(如果之前未初始化)。因此,您将看到的唯一输出是仅包含行1的数据库或仅包含行2的数据库。

1 个答案:

答案 0 :(得分:2)

无法区分新创建的数据库与之前创建的空数据库。

但是,空数据库是唯一存在此问题的数据库。 通过检查sqlite_master表是否为空,可以检测到任何其他数据库。在事务中执行此操作并创建表,并且没有竞争条件。

如果您第一次写入数据库(实际创建文件时)是设置application_id,那么您知道任何具有其他ID的文件不是您的。 (已注册的申请ID是唯一的。)