2008-02-27 20:56:47 +03:00
|
|
|
/*
|
|
|
|
* libcsync -- a library to sync a directory with another
|
|
|
|
*
|
|
|
|
* Copyright (c) 2008 by Andreas Schneider <mail@cynapses.org>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2012-03-02 19:47:34 +04:00
|
|
|
#include "config.h"
|
|
|
|
|
2008-06-09 19:19:12 +04:00
|
|
|
#ifndef _GNU_SOURCE
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#endif
|
|
|
|
|
2008-02-27 20:56:47 +03:00
|
|
|
#include <sqlite3.h>
|
2008-02-29 13:24:14 +03:00
|
|
|
#include <stdio.h>
|
2008-02-27 20:56:47 +03:00
|
|
|
#include <unistd.h>
|
2008-03-20 12:45:05 +03:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
2013-08-15 15:35:24 +04:00
|
|
|
#include <errno.h>
|
2008-02-27 20:56:47 +03:00
|
|
|
|
2008-03-04 12:42:02 +03:00
|
|
|
#include "c_lib.h"
|
2008-02-27 20:56:47 +03:00
|
|
|
#include "csync_private.h"
|
2008-07-09 11:57:19 +04:00
|
|
|
#include "csync_statedb.h"
|
2008-06-28 19:18:10 +04:00
|
|
|
#include "csync_util.h"
|
2012-10-04 15:02:38 +04:00
|
|
|
#include "csync_misc.h"
|
2008-02-27 20:56:47 +03:00
|
|
|
|
2012-10-31 22:26:48 +04:00
|
|
|
#include "c_string.h"
|
2013-07-08 15:37:11 +04:00
|
|
|
#include "c_jhash.h"
|
2012-10-31 22:26:48 +04:00
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
#define CSYNC_LOG_CATEGORY_NAME "csync.statedb"
|
2008-02-27 20:56:47 +03:00
|
|
|
#include "csync_log.h"
|
2013-07-08 15:37:11 +04:00
|
|
|
#include "csync_rename.h"
|
2008-02-27 20:56:47 +03:00
|
|
|
|
2008-05-06 16:31:36 +04:00
|
|
|
#define BUF_SIZE 16
|
2013-07-12 18:22:58 +04:00
|
|
|
#define HASH_QUERY "SELECT * FROM metadata WHERE phash=?1"
|
|
|
|
|
|
|
|
static sqlite3_stmt* _by_hash_stmt = NULL;
|
2008-05-06 16:31:36 +04:00
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
void csync_set_statedb_exists(CSYNC *ctx, int val) {
|
|
|
|
ctx->statedb.exists = val;
|
2008-06-24 13:13:17 +04:00
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_get_statedb_exists(CSYNC *ctx) {
|
|
|
|
return ctx->statedb.exists;
|
2008-06-24 13:13:17 +04:00
|
|
|
}
|
|
|
|
|
2012-11-01 00:08:40 +04:00
|
|
|
/* Set the hide attribute in win32. That makes it invisible in normal explorers */
|
2012-10-31 22:26:48 +04:00
|
|
|
static void _csync_win32_hide_file( const char *file ) {
|
|
|
|
#ifdef _WIN32
|
2012-12-07 16:02:46 +04:00
|
|
|
_TCHAR *fileName;
|
2013-07-31 15:12:10 +04:00
|
|
|
DWORD dwAttrs;
|
|
|
|
|
2012-10-31 22:26:48 +04:00
|
|
|
if( !file ) return;
|
|
|
|
|
|
|
|
fileName = c_multibyte( file );
|
2013-07-31 15:12:10 +04:00
|
|
|
dwAttrs = GetFileAttributesW(fileName);
|
2012-10-31 22:26:48 +04:00
|
|
|
|
|
|
|
if (dwAttrs==INVALID_FILE_ATTRIBUTES) return;
|
|
|
|
|
|
|
|
if (!(dwAttrs & FILE_ATTRIBUTE_HIDDEN)) {
|
|
|
|
SetFileAttributesW(fileName, dwAttrs | FILE_ATTRIBUTE_HIDDEN );
|
|
|
|
}
|
|
|
|
|
|
|
|
c_free_multibyte(fileName);
|
2012-11-01 00:08:40 +04:00
|
|
|
#else
|
|
|
|
(void) file;
|
2012-10-31 22:26:48 +04:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
static int _csync_check_db_integrity(CSYNC *ctx) {
|
|
|
|
c_strlist_t *result = NULL;
|
|
|
|
int rc = -1;
|
|
|
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (ctx->statedb.db == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = csync_statedb_query(ctx, "PRAGMA quick_check;");
|
|
|
|
if (result != NULL) {
|
|
|
|
/* There is a result */
|
|
|
|
if (result->count > 0) {
|
|
|
|
if (c_streq(result->vector[0], "ok")) {
|
|
|
|
rc = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-10-27 18:05:16 +04:00
|
|
|
static int _csync_statedb_check(CSYNC *ctx, const char *statedb) {
|
2013-07-16 14:22:43 +04:00
|
|
|
int fd = -1;
|
|
|
|
int rc;
|
2013-05-21 17:33:09 +04:00
|
|
|
ssize_t r;
|
|
|
|
char buf[BUF_SIZE] = {0};
|
|
|
|
const _TCHAR *wstatedb;
|
2013-07-16 14:22:43 +04:00
|
|
|
csync_stat_t sb;
|
2008-02-27 20:56:47 +03:00
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
/* check db version */
|
2012-03-07 17:46:47 +04:00
|
|
|
#ifdef _WIN32
|
2013-05-21 17:33:09 +04:00
|
|
|
_fmode = _O_BINARY;
|
2012-03-07 17:46:47 +04:00
|
|
|
#endif
|
2012-11-27 18:39:06 +04:00
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
wstatedb = c_multibyte(statedb);
|
|
|
|
fd = _topen(wstatedb, O_RDONLY);
|
|
|
|
|
|
|
|
if (fd >= 0) {
|
2013-07-09 19:30:40 +04:00
|
|
|
/* Check size. Size of zero is a valid database actually. */
|
2013-07-31 15:12:10 +04:00
|
|
|
rc = _tfstat(fd, &sb);
|
2013-07-09 19:30:40 +04:00
|
|
|
|
|
|
|
if (rc == 0) {
|
|
|
|
if (sb.st_size == 0) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Database size is zero byte!");
|
|
|
|
} else {
|
|
|
|
r = read(fd, (void *) buf, sizeof(buf) - 1);
|
|
|
|
close(fd);
|
|
|
|
if (r >= 0) {
|
|
|
|
buf[BUF_SIZE - 1] = '\0';
|
|
|
|
if (c_streq(buf, "SQLite format 3")) {
|
|
|
|
if (sqlite3_open(statedb, &ctx->statedb.db ) == SQLITE_OK) {
|
|
|
|
rc = _csync_check_db_integrity(ctx);
|
2013-08-15 20:37:30 +04:00
|
|
|
if( sqlite3_close(ctx->statedb.db) != 0 ) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "WARN: sqlite3_close error!");
|
|
|
|
}
|
2013-07-09 19:30:40 +04:00
|
|
|
ctx->statedb.db = 0;
|
|
|
|
|
|
|
|
if( rc >= 0 ) {
|
|
|
|
/* everything is fine */
|
|
|
|
c_free_multibyte(wstatedb);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Integrity check failed!");
|
|
|
|
} else {
|
|
|
|
// FIXME: Better error analysis.
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "database corrupted, removing!");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "sqlite version mismatch");
|
2013-05-21 17:33:09 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
2013-07-09 19:30:40 +04:00
|
|
|
/* if it comes here, the database is broken and should be recreated. */
|
2013-05-21 17:33:09 +04:00
|
|
|
_tunlink(wstatedb);
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
c_free_multibyte(wstatedb);
|
2008-02-27 20:56:47 +03:00
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
/* create database */
|
|
|
|
rc = sqlite3_open(statedb, &ctx->statedb.db);
|
2013-08-15 20:37:30 +04:00
|
|
|
if( sqlite3_close(ctx->statedb.db) != 0 ) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "WARN: sqlite3_close error!");
|
|
|
|
}
|
2013-05-22 14:17:44 +04:00
|
|
|
ctx->statedb.db = 0;
|
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
if (rc == SQLITE_OK) {
|
|
|
|
_csync_win32_hide_file(statedb);
|
2013-05-22 18:10:28 +04:00
|
|
|
return 1;
|
2013-05-21 17:33:09 +04:00
|
|
|
}
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite3_open failed: %s %s", sqlite3_errmsg(ctx->statedb.db), statedb);
|
|
|
|
return -1;
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
static int _csync_statedb_is_empty(CSYNC *ctx) {
|
2008-02-27 20:56:47 +03:00
|
|
|
c_strlist_t *result = NULL;
|
|
|
|
int rc = 0;
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
result = csync_statedb_query(ctx, "SELECT COUNT(phash) FROM metadata LIMIT 1 OFFSET 0;");
|
2008-04-29 13:41:16 +04:00
|
|
|
if (result == NULL) {
|
2008-02-27 20:56:47 +03:00
|
|
|
rc = 1;
|
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_statedb_load(CSYNC *ctx, const char *statedb) {
|
2008-02-29 13:24:14 +03:00
|
|
|
int rc = -1;
|
2013-05-22 18:10:28 +04:00
|
|
|
int check_rc = -1;
|
2008-04-29 16:19:50 +04:00
|
|
|
c_strlist_t *result = NULL;
|
2008-07-09 12:10:00 +04:00
|
|
|
char *statedb_tmp = NULL;
|
2008-02-29 13:24:14 +03:00
|
|
|
|
2012-10-04 15:02:38 +04:00
|
|
|
/* csync_statedb_check tries to open the statedb and creates it in case
|
|
|
|
* its not there.
|
|
|
|
*/
|
2013-05-22 18:10:28 +04:00
|
|
|
check_rc = _csync_statedb_check(ctx, statedb);
|
|
|
|
if (check_rc < 0) {
|
2013-04-15 19:47:50 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: checking csync database failed - bail out.");
|
|
|
|
|
2008-02-29 13:24:14 +03:00
|
|
|
rc = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We want a two phase commit for the jounal, so we create a temporary copy
|
|
|
|
* of the database.
|
|
|
|
* The intention is that if something goes wrong we will not loose the
|
2008-07-09 12:10:00 +04:00
|
|
|
* statedb.
|
2008-02-29 13:24:14 +03:00
|
|
|
*/
|
2008-07-09 12:10:00 +04:00
|
|
|
if (asprintf(&statedb_tmp, "%s.ctmp", statedb) < 0) {
|
2013-04-15 19:47:50 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: could not create statedb name - bail out.");
|
|
|
|
|
2008-02-29 13:24:14 +03:00
|
|
|
rc = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
if (c_copy(statedb, statedb_tmp, 0644) < 0) {
|
2013-04-15 19:47:50 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: Failed to copy statedb -> statedb_tmp - bail out.");
|
|
|
|
|
2008-02-29 13:24:14 +03:00
|
|
|
rc = -1;
|
|
|
|
goto out;
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
2012-10-31 22:26:48 +04:00
|
|
|
_csync_win32_hide_file( statedb_tmp );
|
|
|
|
|
2013-04-15 19:47:50 +04:00
|
|
|
/* Open or create the temporary database */
|
2008-07-09 12:10:00 +04:00
|
|
|
if (sqlite3_open(statedb_tmp, &ctx->statedb.db) != SQLITE_OK) {
|
2013-04-15 19:47:50 +04:00
|
|
|
const char *errmsg= sqlite3_errmsg(ctx->statedb.db);
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: Failed to sqlite3 open statedb - bail out: %s.",
|
|
|
|
errmsg ? errmsg : "<no sqlite3 errormsg>");
|
|
|
|
|
2008-02-29 13:24:14 +03:00
|
|
|
rc = -1;
|
|
|
|
goto out;
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
2013-05-22 18:10:28 +04:00
|
|
|
/* If check_rc == 1 the database is new and empty as a result. */
|
|
|
|
if ((check_rc == 1) || _csync_statedb_is_empty(ctx)) {
|
2008-07-09 12:10:00 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "statedb doesn't exist");
|
|
|
|
csync_set_statedb_exists(ctx, 0);
|
2008-02-27 20:56:47 +03:00
|
|
|
} else {
|
2008-07-09 12:10:00 +04:00
|
|
|
csync_set_statedb_exists(ctx, 1);
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
2008-04-29 16:19:50 +04:00
|
|
|
/* optimization for speeding up SQLite */
|
2013-05-21 17:33:09 +04:00
|
|
|
result = csync_statedb_query(ctx, "PRAGMA synchronous = FULL;");
|
2008-04-29 16:19:50 +04:00
|
|
|
c_strlist_destroy(result);
|
2012-10-27 14:03:39 +04:00
|
|
|
result = csync_statedb_query(ctx, "PRAGMA case_sensitive_like = ON;");
|
|
|
|
c_strlist_destroy(result);
|
2008-04-29 16:19:50 +04:00
|
|
|
|
|
|
|
rc = 0;
|
2008-02-29 13:24:14 +03:00
|
|
|
out:
|
2008-07-09 12:10:00 +04:00
|
|
|
SAFE_FREE(statedb_tmp);
|
2008-04-29 16:19:50 +04:00
|
|
|
return rc;
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_statedb_write(CSYNC *ctx) {
|
2013-05-13 17:29:29 +04:00
|
|
|
bool recreate_db = false;
|
2013-05-21 17:33:09 +04:00
|
|
|
|
2008-04-29 13:21:43 +04:00
|
|
|
/* drop tables */
|
2008-07-09 12:10:00 +04:00
|
|
|
if (csync_statedb_drop_tables(ctx) < 0) {
|
2013-05-13 17:29:29 +04:00
|
|
|
recreate_db = true;
|
2008-04-29 13:21:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* create tables */
|
2013-05-13 17:29:29 +04:00
|
|
|
if (! recreate_db) {
|
|
|
|
if (csync_statedb_create_tables(ctx) < 0) {
|
|
|
|
recreate_db = true;
|
|
|
|
}
|
2013-05-13 13:57:25 +04:00
|
|
|
}
|
|
|
|
|
2013-05-13 17:29:29 +04:00
|
|
|
if (recreate_db) {
|
2013-05-13 13:57:25 +04:00
|
|
|
char *statedb_tmp;
|
2013-05-21 17:32:16 +04:00
|
|
|
_TCHAR *wstatedb_tmp = NULL;
|
|
|
|
|
2013-05-13 13:57:25 +04:00
|
|
|
int rc;
|
|
|
|
if (asprintf(&statedb_tmp, "%s.ctmp", ctx->statedb.file) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* close the temporary database */
|
2013-08-15 20:37:30 +04:00
|
|
|
rc = sqlite3_close(ctx->statedb.db);
|
|
|
|
if( rc == SQLITE_BUSY ) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "WARN: sqlite3_close got busy!");
|
|
|
|
}
|
2013-05-21 17:32:16 +04:00
|
|
|
|
2013-05-13 13:57:25 +04:00
|
|
|
/* remove a possible corrupted file if it exists */
|
2013-05-21 17:32:16 +04:00
|
|
|
wstatedb_tmp = c_multibyte(statedb_tmp);
|
|
|
|
_tunlink(wstatedb_tmp);
|
|
|
|
c_free_multibyte(wstatedb_tmp);
|
|
|
|
|
2013-05-13 13:57:25 +04:00
|
|
|
rc = sqlite3_open(statedb_tmp, &ctx->statedb.db);
|
|
|
|
SAFE_FREE(statedb_tmp);
|
|
|
|
if (rc != SQLITE_OK) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* create tables */
|
|
|
|
if (csync_statedb_create_tables(ctx) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2008-04-29 13:21:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* insert metadata */
|
2008-07-09 12:10:00 +04:00
|
|
|
if (csync_statedb_insert_metadata(ctx) < 0) {
|
2008-04-29 13:21:43 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-02-27 15:34:42 +04:00
|
|
|
/* progress info */
|
2013-05-06 19:14:17 +04:00
|
|
|
if (csync_statedb_write_progressinfo(ctx, ctx->progress_info) < 0) {
|
2013-02-27 15:34:42 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-04-29 13:21:43 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_statedb_close(CSYNC *ctx, const char *statedb, int jwritten) {
|
|
|
|
char *statedb_tmp = NULL;
|
2008-04-29 13:21:43 +04:00
|
|
|
int rc = 0;
|
2013-05-21 17:32:16 +04:00
|
|
|
_TCHAR *wstatedb_tmp = NULL;
|
2008-04-29 13:21:43 +04:00
|
|
|
|
2013-07-12 18:37:33 +04:00
|
|
|
/* deallocate query resources */
|
|
|
|
rc = sqlite3_finalize(_by_hash_stmt);
|
|
|
|
_by_hash_stmt = NULL;
|
|
|
|
|
2008-04-29 13:21:43 +04:00
|
|
|
/* close the temporary database */
|
2013-05-22 14:17:44 +04:00
|
|
|
rc = sqlite3_close(ctx->statedb.db);
|
|
|
|
if( rc == SQLITE_BUSY ) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "WARN: sqlite3_close got busy!");
|
|
|
|
}
|
2013-08-05 16:58:43 +04:00
|
|
|
ctx->statedb.db = NULL;
|
2008-04-29 13:21:43 +04:00
|
|
|
|
2013-07-12 18:37:33 +04:00
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
if (asprintf(&statedb_tmp, "%s.ctmp", statedb) < 0) {
|
2008-05-20 18:14:14 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-07-04 13:11:40 +04:00
|
|
|
/* If we successfully synchronized, overwrite the original statedb */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check the integrity of the tmp db. If ok, overwrite the old database with
|
|
|
|
* the tmp db.
|
|
|
|
*/
|
2008-04-29 13:21:43 +04:00
|
|
|
if (jwritten) {
|
2013-07-09 19:31:06 +04:00
|
|
|
/* statedb check returns either
|
|
|
|
* 0 : database exists and is fine
|
|
|
|
* 1 : new database was set up
|
|
|
|
* -1 : error.
|
|
|
|
*/
|
|
|
|
if (_csync_statedb_check(ctx, statedb_tmp) >= 0) {
|
2013-07-04 13:11:40 +04:00
|
|
|
/* New statedb is valid. */
|
|
|
|
|
|
|
|
/* Move the tmp-db to the real one. */
|
|
|
|
if (c_rename(statedb_tmp, statedb) < 0) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
|
2013-08-15 15:35:24 +04:00
|
|
|
"Renaming tmp db to original db failed. (errno=%d)", errno);
|
2013-07-04 13:11:40 +04:00
|
|
|
rc = -1;
|
|
|
|
} else {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
|
|
|
|
"Successfully moved tmp db to original db.");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
wstatedb_tmp = c_multibyte(statedb_tmp);
|
|
|
|
_tunlink(wstatedb_tmp);
|
|
|
|
|
|
|
|
/* new statedb_tmp is not integer. */
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, " ## csync tmp statedb corrupt. Original one is not replaced. ");
|
|
|
|
c_free_multibyte(wstatedb_tmp);
|
|
|
|
rc = -1;
|
|
|
|
}
|
2013-05-21 17:32:16 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
wstatedb_tmp = c_multibyte(statedb_tmp);
|
|
|
|
if (wstatedb_tmp) {
|
|
|
|
_tunlink(wstatedb_tmp);
|
|
|
|
c_free_multibyte(wstatedb_tmp);
|
2008-04-29 13:21:43 +04:00
|
|
|
}
|
2013-05-21 17:32:16 +04:00
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
SAFE_FREE(statedb_tmp);
|
2008-04-29 13:21:43 +04:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_statedb_create_tables(CSYNC *ctx) {
|
2008-04-29 11:24:28 +04:00
|
|
|
c_strlist_t *result = NULL;
|
2012-11-14 20:24:13 +04:00
|
|
|
int rc;
|
|
|
|
char *stmt;
|
2008-04-29 11:24:28 +04:00
|
|
|
|
2008-04-29 16:21:16 +04:00
|
|
|
/*
|
|
|
|
* Create temorary table to work on, this speeds up the
|
2008-07-09 12:10:00 +04:00
|
|
|
* creation of the statedb.
|
2008-04-29 16:21:16 +04:00
|
|
|
*/
|
2008-07-09 12:10:00 +04:00
|
|
|
result = csync_statedb_query(ctx,
|
2013-05-21 17:33:09 +04:00
|
|
|
"CREATE TABLE IF NOT EXISTS metadata_temp("
|
2008-04-29 11:24:28 +04:00
|
|
|
"phash INTEGER(8),"
|
|
|
|
"pathlen INTEGER,"
|
2008-04-29 13:21:43 +04:00
|
|
|
"path VARCHAR(4096),"
|
2008-04-29 11:24:28 +04:00
|
|
|
"inode INTEGER,"
|
|
|
|
"uid INTEGER,"
|
|
|
|
"gid INTEGER,"
|
2008-04-29 16:21:16 +04:00
|
|
|
"mode INTEGER,"
|
2008-04-29 11:24:28 +04:00
|
|
|
"modtime INTEGER(8),"
|
2012-08-23 18:53:53 +04:00
|
|
|
"type INTEGER,"
|
2012-07-24 11:36:06 +04:00
|
|
|
"md5 VARCHAR(32),"
|
2008-04-29 11:24:28 +04:00
|
|
|
"PRIMARY KEY(phash)"
|
2008-04-29 13:21:43 +04:00
|
|
|
");"
|
|
|
|
);
|
2008-04-29 11:24:28 +04:00
|
|
|
|
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
2008-04-29 16:21:16 +04:00
|
|
|
c_strlist_destroy(result);
|
2008-04-29 11:24:28 +04:00
|
|
|
|
2013-06-07 19:15:52 +04:00
|
|
|
/*
|
|
|
|
* Create 'real' table if not existing. That is only important at the
|
|
|
|
* first sync so that other functions do not complain about missing
|
|
|
|
* tables.
|
|
|
|
*/
|
|
|
|
result = csync_statedb_query(ctx,
|
|
|
|
"CREATE TABLE IF NOT EXISTS metadata("
|
|
|
|
"phash INTEGER(8),"
|
|
|
|
"pathlen INTEGER,"
|
|
|
|
"path VARCHAR(4096),"
|
|
|
|
"inode INTEGER,"
|
|
|
|
"uid INTEGER,"
|
|
|
|
"gid INTEGER,"
|
|
|
|
"mode INTEGER,"
|
|
|
|
"modtime INTEGER(8),"
|
|
|
|
"type INTEGER,"
|
|
|
|
"md5 VARCHAR(32),"
|
|
|
|
"PRIMARY KEY(phash)"
|
|
|
|
");"
|
|
|
|
);
|
|
|
|
|
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
2012-11-14 20:24:13 +04:00
|
|
|
result = csync_statedb_query(ctx,
|
|
|
|
"CREATE TABLE IF NOT EXISTS version("
|
|
|
|
"major INTEGER(8),"
|
|
|
|
"minor INTEGER(8),"
|
|
|
|
"patch INTEGER(8),"
|
|
|
|
"custom VARCHAR(256));" );
|
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
2013-02-27 15:34:42 +04:00
|
|
|
|
|
|
|
result = csync_statedb_query(ctx,
|
|
|
|
"CREATE TABLE IF NOT EXISTS progress("
|
|
|
|
"phash INTEGER(8),"
|
|
|
|
"modtime INTEGER(8),"
|
|
|
|
"md5 VARCHAR(32),"
|
2013-03-11 22:02:18 +04:00
|
|
|
"chunk INTEGER(4),"
|
|
|
|
"transferid INTEGER(4),"
|
2013-02-27 15:34:42 +04:00
|
|
|
"error_count INTEGER(8),"
|
|
|
|
"tmpfile VARCHAR(4096),"
|
2013-03-28 16:21:55 +04:00
|
|
|
"error_string VARCHAR(4096),"
|
2013-02-27 15:34:42 +04:00
|
|
|
"PRIMARY KEY(phash)"
|
|
|
|
");"
|
|
|
|
);
|
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
2012-11-14 20:24:13 +04:00
|
|
|
|
|
|
|
/* write the version table. */
|
|
|
|
stmt = sqlite3_mprintf( "INSERT INTO version (major, minor, patch) VALUES (%d, %d, %d);",
|
|
|
|
LIBCSYNC_VERSION_MAJOR, LIBCSYNC_VERSION_MINOR, LIBCSYNC_VERSION_MICRO );
|
|
|
|
|
|
|
|
rc = csync_statedb_insert(ctx, stmt);
|
|
|
|
|
|
|
|
if( rc < 0 ) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Error: Failed to insert into version table.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
sqlite3_free(stmt);
|
2013-02-27 15:34:42 +04:00
|
|
|
|
2012-11-14 20:24:13 +04:00
|
|
|
|
2008-04-29 11:24:28 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_statedb_drop_tables(CSYNC *ctx) {
|
2008-04-29 13:21:43 +04:00
|
|
|
c_strlist_t *result = NULL;
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
result = csync_statedb_query(ctx,
|
2013-05-21 17:33:09 +04:00
|
|
|
"DROP TABLE IF EXISTS metadata_temp;"
|
2008-04-29 13:21:43 +04:00
|
|
|
);
|
2008-04-30 12:39:08 +04:00
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
2008-04-29 13:21:43 +04:00
|
|
|
c_strlist_destroy(result);
|
2013-02-27 15:34:42 +04:00
|
|
|
|
|
|
|
result = csync_statedb_query(ctx,
|
|
|
|
"DROP TABLE IF EXISTS progress;"
|
|
|
|
);
|
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
2013-04-02 16:44:01 +04:00
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
2008-04-29 13:21:43 +04:00
|
|
|
|
2013-03-22 14:04:04 +04:00
|
|
|
result = csync_statedb_query(ctx,
|
|
|
|
"DROP TABLE IF EXISTS version;"
|
|
|
|
);
|
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
2013-02-27 15:34:42 +04:00
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
2008-04-29 13:21:43 +04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-06-18 12:43:57 +04:00
|
|
|
static int _insert_metadata_visitor(void *obj, void *data) {
|
2008-04-29 13:21:43 +04:00
|
|
|
csync_file_stat_t *fs = NULL;
|
|
|
|
CSYNC *ctx = NULL;
|
2013-05-21 17:33:09 +04:00
|
|
|
sqlite3_stmt *stmt = NULL;
|
2013-05-22 14:18:52 +04:00
|
|
|
const char *md5 = "";
|
2013-07-08 15:37:11 +04:00
|
|
|
uint64_t phash = 0;
|
|
|
|
size_t pathlen;
|
|
|
|
const char *path;
|
2008-04-29 13:21:43 +04:00
|
|
|
int rc = -1;
|
|
|
|
|
|
|
|
fs = (csync_file_stat_t *) obj;
|
|
|
|
ctx = (CSYNC *) data;
|
2013-05-21 17:33:09 +04:00
|
|
|
if (ctx == NULL) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Statement visitor data invalid!");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = csync_get_userdata(ctx);
|
|
|
|
if (stmt == NULL) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Statement visitor data invalid!");
|
|
|
|
return -1;
|
|
|
|
}
|
2008-04-29 13:21:43 +04:00
|
|
|
|
2008-06-28 19:18:10 +04:00
|
|
|
switch (fs->instruction) {
|
2008-06-27 19:58:32 +04:00
|
|
|
/*
|
2008-07-09 12:10:00 +04:00
|
|
|
* Don't write ignored, deleted or files with an error to the statedb.
|
2008-06-28 19:18:10 +04:00
|
|
|
* They will be visited on the next synchronization again as a new file.
|
2008-06-27 19:58:32 +04:00
|
|
|
*/
|
2008-06-28 19:18:10 +04:00
|
|
|
case CSYNC_INSTRUCTION_DELETED:
|
|
|
|
case CSYNC_INSTRUCTION_IGNORE:
|
|
|
|
case CSYNC_INSTRUCTION_ERROR:
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
|
|
case CSYNC_INSTRUCTION_NONE:
|
|
|
|
/* As we only sync the local tree we need this flag here */
|
|
|
|
case CSYNC_INSTRUCTION_UPDATED:
|
2013-05-21 17:33:09 +04:00
|
|
|
case CSYNC_INSTRUCTION_CONFLICT:
|
2013-07-08 15:37:11 +04:00
|
|
|
|
|
|
|
path = csync_rename_adjust_path(ctx, fs->path);
|
|
|
|
pathlen = strlen(path);
|
|
|
|
phash = c_jhash64((uint8_t *) path, pathlen, 0);
|
|
|
|
|
|
|
|
|
2008-06-28 19:18:10 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE,
|
2013-05-21 17:33:09 +04:00
|
|
|
"SQL statement: INSERT INTO metadata_temp \n"
|
|
|
|
"\t\t\t(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5) VALUES \n"
|
|
|
|
"\t\t\t(%lld, %lu, %s, %lld, %u, %u, %u, %lu, %d, %s);",
|
2013-07-08 15:37:11 +04:00
|
|
|
(long long signed int) phash,
|
|
|
|
(long unsigned int) pathlen,
|
|
|
|
path,
|
2013-05-21 17:33:09 +04:00
|
|
|
(long long signed int) fs->inode,
|
|
|
|
fs->uid,
|
|
|
|
fs->gid,
|
|
|
|
fs->mode,
|
|
|
|
fs->modtime,
|
|
|
|
fs->type,
|
2012-09-26 17:32:38 +04:00
|
|
|
fs->md5 ? fs->md5 : "<empty>");
|
2008-06-28 19:18:10 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The phash needs to be long long unsigned int or it segfaults on PPC
|
|
|
|
*/
|
2013-07-08 15:37:11 +04:00
|
|
|
sqlite3_bind_int64(stmt, 1, (long long signed int) phash);
|
|
|
|
sqlite3_bind_int64(stmt, 2, (long unsigned int) pathlen);
|
|
|
|
sqlite3_bind_text( stmt, 3, path, pathlen, SQLITE_STATIC);
|
2013-05-21 17:33:09 +04:00
|
|
|
sqlite3_bind_int64(stmt, 4, (long long signed int) fs->inode);
|
|
|
|
sqlite3_bind_int( stmt, 5, fs->uid);
|
|
|
|
sqlite3_bind_int( stmt, 6, fs->gid);
|
|
|
|
sqlite3_bind_int( stmt, 7, fs->mode);
|
2013-08-07 18:58:40 +04:00
|
|
|
sqlite3_bind_int64(stmt, 8, (long long signed int) fs->modtime);
|
2013-05-21 17:33:09 +04:00
|
|
|
sqlite3_bind_int( stmt, 9, fs->type);
|
2013-05-22 14:18:52 +04:00
|
|
|
|
|
|
|
/* The md5 sum might be zero for directories. They will be investigated in the next
|
|
|
|
* sync, called "Oliviers patch". */
|
|
|
|
if (fs->md5 != NULL) {
|
|
|
|
md5 = fs->md5;
|
|
|
|
}
|
|
|
|
sqlite3_bind_text( stmt,10, md5, strlen(md5), SQLITE_STATIC);
|
2013-05-21 17:33:09 +04:00
|
|
|
|
|
|
|
rc = sqlite3_step(stmt);
|
|
|
|
if ( rc != SQLITE_DONE) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "sqlite insert failed: %s", sqlite3_errmsg(ctx->statedb.db));
|
|
|
|
rc = -1;
|
2008-06-28 19:18:10 +04:00
|
|
|
}
|
2008-04-29 13:21:43 +04:00
|
|
|
|
2013-07-08 15:37:11 +04:00
|
|
|
sqlite3_reset(stmt);
|
|
|
|
SAFE_FREE(path);
|
2008-06-28 19:18:10 +04:00
|
|
|
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN,
|
2008-07-09 12:10:00 +04:00
|
|
|
"file: %s, instruction: %s (%d), not added to statedb!",
|
2008-06-28 19:18:10 +04:00
|
|
|
fs->path, csync_instruction_str(fs->instruction), fs->instruction);
|
|
|
|
rc = 1;
|
|
|
|
break;
|
2008-06-18 11:56:08 +04:00
|
|
|
}
|
2008-04-29 13:21:43 +04:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_statedb_insert_metadata(CSYNC *ctx) {
|
2008-04-29 16:21:16 +04:00
|
|
|
c_strlist_t *result = NULL;
|
2013-05-21 17:33:09 +04:00
|
|
|
char buffer[] = "INSERT INTO metadata_temp VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)";
|
|
|
|
sqlite3_stmt* stmt;
|
|
|
|
int rc;
|
2008-04-29 16:21:16 +04:00
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
/* start a transaction */
|
|
|
|
result = csync_statedb_query(ctx, "BEGIN TRANSACTION;");
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
/* prepare the INSERT statement */
|
|
|
|
rc = sqlite3_prepare_v2(ctx->statedb.db, buffer, strlen(buffer), &stmt, NULL);
|
|
|
|
if( rc != SQLITE_OK ) {
|
2008-04-29 16:21:16 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
/* and store the insert statement handle to the ctx as userdata. */
|
|
|
|
csync_set_userdata(ctx, stmt);
|
|
|
|
|
|
|
|
rc = c_rbtree_walk(ctx->local.tree, ctx, _insert_metadata_visitor);
|
2013-05-22 14:19:31 +04:00
|
|
|
sqlite3_finalize( stmt );
|
2013-05-21 17:33:09 +04:00
|
|
|
|
|
|
|
/* Commit the result even if there was an error */
|
|
|
|
result = csync_statedb_query(ctx, "COMMIT TRANSACTION;");
|
2013-05-22 14:19:31 +04:00
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
/* FIXME: How do we deal with an error in rbtree_walk? No rollback needed actually */
|
|
|
|
if (rc < 0) {
|
|
|
|
/* We stay with metadata and remove the tmp database */
|
|
|
|
result = csync_statedb_query(ctx, "DROP TABLE metadata_temp;");
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
2008-04-29 16:21:16 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
/* If all goes well, drop metadata and rename metadata_temp */
|
|
|
|
result = csync_statedb_query(ctx, "BEGIN TRANSACTION;");
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
result = csync_statedb_query(ctx, "DROP TABLE IF EXISTS metadata;");
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
result = csync_statedb_query(ctx, "ALTER TABLE metadata_temp RENAME TO metadata;");
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
result = csync_statedb_query(ctx,
|
|
|
|
"CREATE INDEX IF NOT EXISTS metadata_phash ON metadata(phash);");
|
|
|
|
if (result == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
result = csync_statedb_query(ctx,
|
|
|
|
"CREATE INDEX IF NOT EXISTS metadata_inode ON metadata(inode);");
|
2008-04-30 14:40:31 +04:00
|
|
|
if (result == NULL) {
|
2013-05-21 17:33:09 +04:00
|
|
|
return -1;
|
2008-04-30 14:40:31 +04:00
|
|
|
}
|
2013-05-21 17:33:09 +04:00
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
2008-04-30 14:40:31 +04:00
|
|
|
|
2013-05-21 17:33:09 +04:00
|
|
|
result = csync_statedb_query(ctx, "COMMIT TRANSACTION;");
|
2008-04-30 14:40:31 +04:00
|
|
|
c_strlist_destroy(result);
|
2008-04-29 16:21:16 +04:00
|
|
|
|
|
|
|
return 0;
|
2008-04-29 13:21:43 +04:00
|
|
|
}
|
2008-02-27 20:56:47 +03:00
|
|
|
|
2008-05-09 13:25:21 +04:00
|
|
|
/* caller must free the memory */
|
2008-07-09 12:10:00 +04:00
|
|
|
csync_file_stat_t *csync_statedb_get_stat_by_hash(CSYNC *ctx, uint64_t phash) {
|
2008-04-30 18:23:42 +04:00
|
|
|
csync_file_stat_t *st = NULL;
|
|
|
|
size_t len = 0;
|
2013-07-12 18:22:58 +04:00
|
|
|
int column_count = 0;
|
|
|
|
int rc;
|
2008-04-30 18:23:42 +04:00
|
|
|
|
2013-07-12 18:22:58 +04:00
|
|
|
if( _by_hash_stmt == NULL ) {
|
|
|
|
rc = sqlite3_prepare_v2(ctx->statedb.db, HASH_QUERY, strlen(HASH_QUERY), &_by_hash_stmt, NULL);
|
|
|
|
if( rc != SQLITE_OK ) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for hash query.");
|
|
|
|
return NULL;
|
|
|
|
}
|
2008-04-30 18:23:42 +04:00
|
|
|
}
|
|
|
|
|
2013-07-12 18:22:58 +04:00
|
|
|
if( _by_hash_stmt == NULL ) {
|
2008-04-30 18:23:42 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-07-12 18:22:58 +04:00
|
|
|
column_count = sqlite3_column_count(_by_hash_stmt);
|
|
|
|
|
|
|
|
sqlite3_bind_int64(_by_hash_stmt, 1, (long long signed int)phash);
|
|
|
|
rc = sqlite3_step(_by_hash_stmt);
|
|
|
|
|
|
|
|
if( rc == SQLITE_ROW ) {
|
|
|
|
if(column_count > 7) {
|
2012-08-28 15:09:54 +04:00
|
|
|
/* phash, pathlen, path, inode, uid, gid, mode, modtime */
|
2013-07-12 18:22:58 +04:00
|
|
|
len = sqlite3_column_int(_by_hash_stmt, 1);
|
2012-08-28 15:09:54 +04:00
|
|
|
st = c_malloc(sizeof(csync_file_stat_t) + len + 1);
|
|
|
|
if (st == NULL) {
|
2013-07-12 18:22:58 +04:00
|
|
|
return NULL;
|
2012-08-28 15:09:54 +04:00
|
|
|
}
|
2012-10-04 15:03:42 +04:00
|
|
|
/* clear the whole structure */
|
|
|
|
ZERO_STRUCTP(st);
|
2008-09-03 11:46:19 +04:00
|
|
|
|
2012-08-28 15:09:54 +04:00
|
|
|
/*
|
2013-07-12 18:22:58 +04:00
|
|
|
* FIXME:
|
|
|
|
* We use an INTEGER(8) which is signed to the phash in the sqlite3 db,
|
|
|
|
* but the phash is an uint64_t. So for some values we get a string like
|
|
|
|
* "1.66514565505016e+19". For such a string strtoull() returns 1.
|
|
|
|
* phash = 1
|
|
|
|
*
|
|
|
|
* st->phash = strtoull(result->vector[0], NULL, 10);
|
|
|
|
*/
|
2008-09-03 11:46:19 +04:00
|
|
|
|
2012-08-28 15:09:54 +04:00
|
|
|
/* The query suceeded so use the phash we pass to the function. */
|
|
|
|
st->phash = phash;
|
2008-09-03 11:46:19 +04:00
|
|
|
|
2013-07-12 18:22:58 +04:00
|
|
|
st->pathlen = sqlite3_column_int(_by_hash_stmt, 1);
|
|
|
|
memcpy(st->path, (len ? (char*) sqlite3_column_text(_by_hash_stmt, 2) : ""), len + 1);
|
|
|
|
st->inode = sqlite3_column_int(_by_hash_stmt,3);
|
|
|
|
st->uid = sqlite3_column_int(_by_hash_stmt, 4);
|
|
|
|
st->gid = sqlite3_column_int(_by_hash_stmt, 5);
|
|
|
|
st->mode = sqlite3_column_int(_by_hash_stmt, 6);
|
|
|
|
st->modtime = strtoul((char*)sqlite3_column_text(_by_hash_stmt, 7), NULL, 10);
|
2012-10-04 15:03:42 +04:00
|
|
|
|
2013-07-12 18:22:58 +04:00
|
|
|
if(st && column_count > 8 ) {
|
|
|
|
st->type = sqlite3_column_int(_by_hash_stmt, 8);
|
2012-10-04 15:03:42 +04:00
|
|
|
}
|
|
|
|
|
2013-07-12 18:22:58 +04:00
|
|
|
if(column_count > 9 && sqlite3_column_text(_by_hash_stmt, 9)) {
|
|
|
|
st->md5 = c_strdup( (char*) sqlite3_column_text(_by_hash_stmt, 9) );
|
2012-10-04 15:03:42 +04:00
|
|
|
}
|
2013-07-12 18:22:58 +04:00
|
|
|
}
|
2012-08-28 15:09:54 +04:00
|
|
|
} else {
|
2013-07-12 18:22:58 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "sqlite hash query fail: %s", sqlite3_errmsg(ctx->statedb.db));
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "No result record found for phash = %llu",
|
|
|
|
(long long unsigned int) phash);
|
|
|
|
SAFE_FREE(st);
|
2012-08-28 15:09:54 +04:00
|
|
|
}
|
2012-08-26 22:07:36 +04:00
|
|
|
|
2013-07-12 18:22:58 +04:00
|
|
|
sqlite3_reset(_by_hash_stmt);
|
2008-04-30 18:23:42 +04:00
|
|
|
|
|
|
|
return st;
|
|
|
|
}
|
|
|
|
|
2008-05-09 13:25:21 +04:00
|
|
|
/* caller must free the memory */
|
2012-08-03 19:48:44 +04:00
|
|
|
csync_file_stat_t *csync_statedb_get_stat_by_inode(CSYNC *ctx, uint64_t inode) {
|
2008-04-30 18:23:42 +04:00
|
|
|
csync_file_stat_t *st = NULL;
|
|
|
|
c_strlist_t *result = NULL;
|
|
|
|
char *stmt = NULL;
|
|
|
|
size_t len = 0;
|
|
|
|
|
2013-06-11 10:47:32 +04:00
|
|
|
stmt = sqlite3_mprintf("SELECT * FROM metadata WHERE inode='%lld'",
|
|
|
|
(long long unsigned int) inode);
|
2008-04-30 18:23:42 +04:00
|
|
|
if (stmt == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
result = csync_statedb_query(ctx, stmt);
|
2008-04-30 18:23:42 +04:00
|
|
|
sqlite3_free(stmt);
|
|
|
|
if (result == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result->count <= 6) {
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* phash, pathlen, path, inode, uid, gid, mode, modtime */
|
|
|
|
len = strlen(result->vector[2]);
|
|
|
|
st = c_malloc(sizeof(csync_file_stat_t) + len + 1);
|
|
|
|
if (st == NULL) {
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
return NULL;
|
|
|
|
}
|
2012-10-04 15:03:42 +04:00
|
|
|
/* clear the whole structure */
|
|
|
|
ZERO_STRUCTP(st);
|
2008-04-30 18:23:42 +04:00
|
|
|
|
|
|
|
st->phash = strtoull(result->vector[0], NULL, 10);
|
|
|
|
st->pathlen = atoi(result->vector[1]);
|
|
|
|
memcpy(st->path, (len ? result->vector[2] : ""), len + 1);
|
|
|
|
st->inode = atoi(result->vector[3]);
|
|
|
|
st->uid = atoi(result->vector[4]);
|
|
|
|
st->gid = atoi(result->vector[5]);
|
|
|
|
st->mode = atoi(result->vector[6]);
|
|
|
|
st->modtime = strtoul(result->vector[7], NULL, 10);
|
2012-08-23 18:53:53 +04:00
|
|
|
st->type = atoi(result->vector[8]);
|
|
|
|
if( result->vector[9] )
|
|
|
|
st->md5 = c_strdup(result->vector[9]);
|
2008-04-30 18:23:42 +04:00
|
|
|
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
return st;
|
|
|
|
}
|
|
|
|
|
2012-07-31 12:40:46 +04:00
|
|
|
char *csync_statedb_get_uniqId( CSYNC *ctx, uint64_t jHash, csync_vio_file_stat_t *buf ) {
|
|
|
|
char *ret = NULL;
|
|
|
|
c_strlist_t *result = NULL;
|
|
|
|
char *stmt = NULL;
|
2013-04-24 16:09:20 +04:00
|
|
|
(void)buf;
|
2012-07-31 12:40:46 +04:00
|
|
|
|
2013-01-16 19:27:59 +04:00
|
|
|
if( ! csync_get_statedb_exists(ctx)) return ret;
|
|
|
|
|
2013-04-24 16:09:20 +04:00
|
|
|
stmt = sqlite3_mprintf("SELECT md5 FROM metadata WHERE phash='%lld'", jHash);
|
2012-08-30 18:01:52 +04:00
|
|
|
|
2012-07-31 12:40:46 +04:00
|
|
|
result = csync_statedb_query(ctx, stmt);
|
|
|
|
sqlite3_free(stmt);
|
|
|
|
if (result == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result->count == 1) {
|
|
|
|
/* phash, pathlen, path, inode, uid, gid, mode, modtime */
|
|
|
|
ret = c_strdup( result->vector[0] );
|
|
|
|
}
|
|
|
|
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-10-16 15:14:04 +04:00
|
|
|
c_strlist_t *csync_statedb_get_below_path( CSYNC *ctx, const char *path ) {
|
|
|
|
c_strlist_t *list = NULL;
|
|
|
|
char *stmt = NULL;
|
|
|
|
|
|
|
|
stmt = sqlite3_mprintf("SELECT phash, path, inode, uid, gid, mode, modtime, type, md5 "
|
2012-10-27 14:03:39 +04:00
|
|
|
"FROM metadata WHERE path LIKE('%q/%%')", path);
|
2012-10-16 15:14:04 +04:00
|
|
|
if (stmt == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "SQL: %s", stmt);
|
|
|
|
|
|
|
|
list = csync_statedb_query( ctx, stmt );
|
|
|
|
|
|
|
|
sqlite3_free(stmt);
|
|
|
|
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
/* query the statedb, caller must free the memory */
|
|
|
|
c_strlist_t *csync_statedb_query(CSYNC *ctx, const char *statement) {
|
2008-12-23 15:54:57 +03:00
|
|
|
int err = SQLITE_OK;
|
|
|
|
int rc = SQLITE_OK;
|
2008-02-27 20:56:47 +03:00
|
|
|
size_t i = 0;
|
|
|
|
size_t busy_count = 0;
|
|
|
|
size_t retry_count = 0;
|
|
|
|
size_t column_count = 0;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
const char *tail = NULL;
|
|
|
|
c_strlist_t *result = NULL;
|
2012-08-23 18:54:36 +04:00
|
|
|
int row = 0;
|
2008-02-27 20:56:47 +03:00
|
|
|
|
|
|
|
do {
|
|
|
|
/* compile SQL program into a virtual machine, reattempteing if busy */
|
|
|
|
do {
|
|
|
|
if (busy_count) {
|
|
|
|
/* sleep 100 msec */
|
|
|
|
usleep(100000);
|
2008-06-27 20:52:09 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "sqlite3_prepare: BUSY counter: %zu", busy_count);
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
2008-07-09 12:10:00 +04:00
|
|
|
err = sqlite3_prepare(ctx->statedb.db, statement, -1, &stmt, &tail);
|
2008-02-27 20:56:47 +03:00
|
|
|
} while (err == SQLITE_BUSY && busy_count ++ < 120);
|
|
|
|
|
|
|
|
if (err != SQLITE_OK) {
|
|
|
|
if (err == SQLITE_BUSY) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Gave up waiting for lock to clear");
|
|
|
|
}
|
2008-07-09 12:10:00 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "sqlite3_compile error: %s - on query %s", sqlite3_errmsg(ctx->statedb.db), statement);
|
2008-02-27 20:56:47 +03:00
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
busy_count = 0;
|
|
|
|
column_count = sqlite3_column_count(stmt);
|
|
|
|
|
|
|
|
/* execute virtual machine by iterating over rows */
|
|
|
|
for(;;) {
|
|
|
|
err = sqlite3_step(stmt);
|
|
|
|
|
|
|
|
if (err == SQLITE_BUSY) {
|
|
|
|
if (busy_count++ > 120) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Busy counter has reached its maximum. Aborting this sql statement");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* sleep 100 msec */
|
|
|
|
usleep(100000);
|
2008-06-27 20:52:09 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "sqlite3_step: BUSY counter: %zu", busy_count);
|
2008-02-27 20:56:47 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err == SQLITE_MISUSE) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite3_step: MISUSE!!");
|
|
|
|
}
|
|
|
|
|
2008-04-29 13:41:16 +04:00
|
|
|
if (err == SQLITE_DONE) {
|
|
|
|
if (result == NULL) {
|
|
|
|
result = c_strlist_new(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err == SQLITE_ERROR) {
|
2008-02-27 20:56:47 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-10-27 21:27:14 +04:00
|
|
|
row++;
|
2012-08-23 18:54:36 +04:00
|
|
|
if( result ) {
|
|
|
|
result = c_strlist_expand(result, row*column_count);
|
|
|
|
} else {
|
|
|
|
result = c_strlist_new(column_count);
|
|
|
|
}
|
|
|
|
|
2008-02-27 20:56:47 +03:00
|
|
|
if (result == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* iterate over columns */
|
|
|
|
for (i = 0; i < column_count; i++) {
|
2012-08-20 20:10:29 +04:00
|
|
|
// CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "sqlite3_column_text: %s", (char *) sqlite3_column_text(stmt, i));
|
2008-02-27 20:56:47 +03:00
|
|
|
if (c_strlist_add(result, (char *) sqlite3_column_text(stmt, i)) < 0) {
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} /* end infinite for loop */
|
|
|
|
|
|
|
|
/* deallocate vm resources */
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
if (err != SQLITE_DONE && rc != SQLITE_SCHEMA) {
|
2008-07-09 12:10:00 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite_step error: %s - on query: %s", sqlite3_errmsg(ctx->statedb.db), statement);
|
2012-10-19 22:09:00 +04:00
|
|
|
if (result != NULL) {
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
}
|
2013-05-13 13:57:25 +04:00
|
|
|
return NULL;
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (rc == SQLITE_SCHEMA) {
|
|
|
|
retry_count ++;
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "SQLITE_SCHEMA error occurred on query: %s", statement);
|
|
|
|
if (retry_count < 10) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Retrying now.");
|
|
|
|
} else {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "RETRY count has reached its maximum. Aborting statement: %s", statement);
|
2012-10-19 22:09:00 +04:00
|
|
|
if (result != NULL) {
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
}
|
2008-02-27 20:56:47 +03:00
|
|
|
result = c_strlist_new(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (rc == SQLITE_SCHEMA && retry_count < 10);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
int csync_statedb_insert(CSYNC *ctx, const char *statement) {
|
2008-02-27 20:56:47 +03:00
|
|
|
int err;
|
|
|
|
int rc = 0;
|
|
|
|
int busy_count = 0;
|
|
|
|
int retry_count = 0;
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
const char *tail;
|
|
|
|
|
2008-03-04 13:29:43 +03:00
|
|
|
if (!statement[0]) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-02-27 20:56:47 +03:00
|
|
|
do {
|
|
|
|
/* compile SQL program into a virtual machine, reattempteing if busy */
|
|
|
|
do {
|
|
|
|
if (busy_count) {
|
|
|
|
/* sleep 100 msec */
|
|
|
|
usleep(100000);
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "sqlite3_prepare: BUSY counter: %d", busy_count);
|
|
|
|
}
|
2008-07-09 12:10:00 +04:00
|
|
|
err = sqlite3_prepare(ctx->statedb.db, statement, -1, &stmt, &tail);
|
2008-02-27 20:56:47 +03:00
|
|
|
} while (err == SQLITE_BUSY && busy_count++ < 120);
|
|
|
|
|
|
|
|
if (err != SQLITE_OK) {
|
|
|
|
if (err == SQLITE_BUSY) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Gave up waiting for lock to clear");
|
|
|
|
}
|
2008-07-09 12:10:00 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite3_compile error: %s on query %s", sqlite3_errmsg(ctx->statedb.db), statement);
|
2008-02-27 20:56:47 +03:00
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
busy_count = 0;
|
|
|
|
|
|
|
|
/* execute virtual machine by iterating over rows */
|
|
|
|
for(;;) {
|
|
|
|
err = sqlite3_step(stmt);
|
|
|
|
|
|
|
|
if (err == SQLITE_BUSY) {
|
|
|
|
if (busy_count++ > 120) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Busy counter has reached its maximum. Aborting this sql statement");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* sleep 100 msec */
|
|
|
|
usleep(100000);
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "sqlite3_step: BUSY counter: %d", busy_count);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err == SQLITE_MISUSE) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite3_step: MISUSE!!");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err == SQLITE_DONE || err == SQLITE_ERROR) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} /* end infinite for loop */
|
|
|
|
|
|
|
|
/* deallocate vm resources */
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
|
|
|
if (err != SQLITE_DONE && rc != SQLITE_SCHEMA) {
|
2008-07-09 12:10:00 +04:00
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite_step error: %s on insert: %s", sqlite3_errmsg(ctx->statedb.db), statement);
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (rc == SQLITE_SCHEMA) {
|
|
|
|
retry_count++;
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "SQLITE_SCHEMA error occurred on insert: %s", statement);
|
|
|
|
if (retry_count < 10) {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Retrying now.");
|
|
|
|
} else {
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "RETRY count has reached its maximum. Aborting statement: %s", statement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (rc == SQLITE_SCHEMA && retry_count < 10);
|
|
|
|
|
2008-07-09 12:10:00 +04:00
|
|
|
return sqlite3_last_insert_rowid(ctx->statedb.db);
|
2008-02-27 20:56:47 +03:00
|
|
|
}
|
|
|
|
|
2013-02-27 15:34:42 +04:00
|
|
|
csync_progressinfo_t* csync_statedb_get_progressinfo(CSYNC *ctx, uint64_t phash, uint64_t modtime, const char* md5) {
|
|
|
|
char *stmt = NULL;
|
|
|
|
csync_progressinfo_t *ret = NULL;
|
|
|
|
c_strlist_t *result = NULL;
|
|
|
|
|
|
|
|
if( ! csync_get_statedb_exists(ctx)) return ret;
|
2013-05-07 17:20:22 +04:00
|
|
|
stmt = sqlite3_mprintf("SELECT error_count, chunk, transferid, tmpfile, error_string FROM progress WHERE phash='%llu' AND modtime='%lld' AND md5='%q'",
|
2013-02-27 15:34:42 +04:00
|
|
|
(long long unsigned int) phash, (long long signed int) modtime, md5);
|
|
|
|
if (!stmt) return ret;
|
|
|
|
|
|
|
|
result = csync_statedb_query(ctx, stmt);
|
|
|
|
sqlite3_free(stmt);
|
|
|
|
if (result == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-07 17:20:22 +04:00
|
|
|
if (result->count == 5) {
|
2013-02-27 15:34:42 +04:00
|
|
|
ret = c_malloc(sizeof(csync_progressinfo_t));
|
|
|
|
if (!ret) goto out;
|
|
|
|
ret->next = NULL;
|
2013-03-11 22:02:18 +04:00
|
|
|
ret->chunk = atoi(result->vector[1]);
|
2013-02-27 15:34:42 +04:00
|
|
|
ret->error = atoi(result->vector[0]);
|
2013-03-11 22:02:18 +04:00
|
|
|
ret->transferId = atoi(result->vector[2]);
|
|
|
|
ret->tmpfile = c_strdup(result->vector[3]);
|
2013-02-27 15:34:42 +04:00
|
|
|
ret->md5 = md5 ? c_strdup(md5) : NULL;
|
|
|
|
ret->modtime = modtime;
|
|
|
|
ret->phash = phash;
|
2013-05-07 17:20:22 +04:00
|
|
|
ret->error_string = c_strdup(result->vector[4]);
|
2013-02-27 15:34:42 +04:00
|
|
|
}
|
|
|
|
out:
|
|
|
|
c_strlist_destroy(result);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void csync_statedb_free_progressinfo(csync_progressinfo_t* pi)
|
|
|
|
{
|
|
|
|
if (!pi) return;
|
|
|
|
SAFE_FREE(pi->md5);
|
|
|
|
SAFE_FREE(pi->tmpfile);
|
2013-03-28 16:21:55 +04:00
|
|
|
SAFE_FREE(pi->error_string);
|
2013-02-27 15:34:42 +04:00
|
|
|
SAFE_FREE(pi);
|
|
|
|
}
|
|
|
|
|
|
|
|
int csync_statedb_write_progressinfo(CSYNC* ctx, csync_progressinfo_t* pi)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
char *stmt = NULL;
|
|
|
|
|
|
|
|
while (rc > -1 && pi) {
|
|
|
|
stmt = sqlite3_mprintf("INSERT INTO progress "
|
2013-05-07 17:20:22 +04:00
|
|
|
"(phash, modtime, md5, chunk, transferid, error_count, tmpfile, error_string) VALUES"
|
|
|
|
"(%llu, %lld, '%q', %d, %d, %d, '%q', '%q');",
|
2013-02-27 15:34:42 +04:00
|
|
|
(long long signed int) pi->phash,
|
|
|
|
(long long int) pi->modtime,
|
|
|
|
pi->md5,
|
|
|
|
pi->chunk,
|
2013-03-11 22:02:18 +04:00
|
|
|
pi->transferId,
|
2013-02-27 15:34:42 +04:00
|
|
|
pi->error,
|
2013-03-28 16:21:55 +04:00
|
|
|
pi->tmpfile,
|
|
|
|
pi->error_string
|
|
|
|
);
|
2013-02-27 15:34:42 +04:00
|
|
|
|
|
|
|
if (stmt == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "%s" , stmt);
|
|
|
|
|
|
|
|
rc = csync_statedb_insert(ctx, stmt);
|
|
|
|
sqlite3_free(stmt);
|
|
|
|
pi = pi->next;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2009-05-13 12:12:07 +04:00
|
|
|
/* vim: set ts=8 sw=2 et cindent: */
|