]> arthur.barton.de Git - netatalk.git/blobdiff - libatalk/cnid/mysql/cnid_mysql.c
New MySQL CNID backend
[netatalk.git] / libatalk / cnid / mysql / cnid_mysql.c
diff --git a/libatalk/cnid/mysql/cnid_mysql.c b/libatalk/cnid/mysql/cnid_mysql.c
new file mode 100644 (file)
index 0000000..c661f9a
--- /dev/null
@@ -0,0 +1,961 @@
+/*
+ * Copyright (C) Ralph Boehme 2013
+ * All Rights Reserved.  See COPYING.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <netinet/tcp.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <time.h>
+#include <arpa/inet.h>
+
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <errmsg.h>
+
+#include <atalk/logger.h>
+#include <atalk/adouble.h>
+#include <atalk/util.h>
+#include <atalk/cnid_mysql_private.h>
+#include <atalk/cnid_bdb_private.h>
+#include <atalk/errchk.h>
+#include <atalk/globals.h>
+
+static MYSQL_BIND lookup_param[4], lookup_result[5];
+static MYSQL_BIND add_param[4], put_param[5];
+
+/*
+ * Prepared statement parameters
+ */
+static char               stmt_param_name[MAXPATHLEN];
+static unsigned long      stmt_param_name_len;
+static unsigned long long stmt_param_id;
+static unsigned long long stmt_param_did;
+static unsigned long long stmt_param_dev;
+static unsigned long long stmt_param_ino;
+
+/*
+ * lookup result parameters
+ */
+static unsigned long long lookup_result_id;
+static unsigned long long lookup_result_did;
+static char               lookup_result_name[MAXPATHLEN];
+static unsigned long      lookup_result_name_len;
+static unsigned long long lookup_result_dev;
+static unsigned long long lookup_result_ino;
+
+static int init_prepared_stmt_lookup(CNID_mysql_private *db)
+{
+    EC_INIT;
+    char *sql = NULL;
+
+    lookup_param[0].buffer_type    = MYSQL_TYPE_STRING;
+    lookup_param[0].buffer         = &stmt_param_name;
+    lookup_param[0].buffer_length  = sizeof(stmt_param_name);
+    lookup_param[0].length         = &stmt_param_name_len;
+
+    lookup_param[1].buffer_type    = MYSQL_TYPE_LONGLONG;
+    lookup_param[1].buffer         = &stmt_param_did;
+    lookup_param[1].is_unsigned    = true;
+
+    lookup_param[2].buffer_type    = MYSQL_TYPE_LONGLONG;
+    lookup_param[2].buffer         = &stmt_param_dev;
+    lookup_param[2].is_unsigned    = true;
+
+    lookup_param[3].buffer_type    = MYSQL_TYPE_LONGLONG;
+    lookup_param[3].buffer         = &stmt_param_ino;
+    lookup_param[3].is_unsigned    = true;
+
+    lookup_result[0].buffer_type   = MYSQL_TYPE_LONGLONG;
+    lookup_result[0].buffer        = &lookup_result_id;
+    lookup_result[0].is_unsigned   = true;
+
+    lookup_result[1].buffer_type   = MYSQL_TYPE_LONGLONG;
+    lookup_result[1].buffer        = &lookup_result_did;
+    lookup_result[1].is_unsigned   = true;
+
+    lookup_result[2].buffer_type   = MYSQL_TYPE_STRING;
+    lookup_result[2].buffer        = &lookup_result_name;
+    lookup_result[2].buffer_length = sizeof(lookup_result_name);
+    lookup_result[2].length        = &lookup_result_name_len;
+
+    lookup_result[3].buffer_type   = MYSQL_TYPE_LONGLONG;
+    lookup_result[3].buffer        = &lookup_result_dev;
+    lookup_result[3].is_unsigned   = true;
+
+    lookup_result[4].buffer_type   = MYSQL_TYPE_LONGLONG;
+    lookup_result[4].buffer        = &lookup_result_ino;
+    lookup_result[4].is_unsigned   = true;
+
+    EC_NULL( db->cnid_lookup_stmt = mysql_stmt_init(db->cnid_mysql_con) );
+    EC_NEG1( asprintf(&sql,
+                      "SELECT Id,Did,Name,DevNo,InodeNo FROM %s "
+                      "WHERE (Name=? AND Did=?) OR (DevNo=? AND InodeNo=?)",
+                      db->cnid_mysql_voluuid_str) );
+    EC_ZERO_LOG( mysql_stmt_prepare(db->cnid_lookup_stmt, sql, strlen(sql)) );
+    EC_ZERO_LOG( mysql_stmt_bind_param(db->cnid_lookup_stmt, lookup_param) );
+
+EC_CLEANUP:
+    if (sql)
+        free(sql);
+    EC_EXIT;
+}
+
+static int init_prepared_stmt_add(CNID_mysql_private *db)
+{
+    EC_INIT;
+    char *sql = NULL;
+
+    EC_NULL( db->cnid_add_stmt = mysql_stmt_init(db->cnid_mysql_con) );
+    EC_NEG1( asprintf(&sql,
+                      "INSERT INTO %s (Name,Did,DevNo,InodeNo) VALUES(?,?,?,?)",
+                      db->cnid_mysql_voluuid_str) );
+
+    add_param[0].buffer_type    = MYSQL_TYPE_STRING;
+    add_param[0].buffer         = &stmt_param_name;
+    add_param[0].buffer_length  = sizeof(stmt_param_name);
+    add_param[0].length         = &stmt_param_name_len;
+
+    add_param[1].buffer_type    = MYSQL_TYPE_LONGLONG;
+    add_param[1].buffer         = &stmt_param_did;
+    add_param[1].is_unsigned    = true;
+
+    add_param[2].buffer_type    = MYSQL_TYPE_LONGLONG;
+    add_param[2].buffer         = &stmt_param_dev;
+    add_param[2].is_unsigned    = true;
+
+    add_param[3].buffer_type    = MYSQL_TYPE_LONGLONG;
+    add_param[3].buffer         = &stmt_param_ino;
+    add_param[3].is_unsigned    = true;
+
+    EC_ZERO_LOG( mysql_stmt_prepare(db->cnid_add_stmt, sql, strlen(sql)) );
+    EC_ZERO_LOG( mysql_stmt_bind_param(db->cnid_add_stmt, add_param) );
+
+EC_CLEANUP:
+    if (sql)
+        free(sql);
+    EC_EXIT;
+}
+
+static int init_prepared_stmt_put(CNID_mysql_private *db)
+{
+    EC_INIT;
+    char *sql = NULL;
+
+    EC_NULL( db->cnid_put_stmt = mysql_stmt_init(db->cnid_mysql_con) );
+    EC_NEG1( asprintf(&sql,
+                      "INSERT INTO %s (Id,Name,Did,DevNo,InodeNo) VALUES(?,?,?,?,?)",
+                      db->cnid_mysql_voluuid_str) );
+
+    put_param[0].buffer_type    = MYSQL_TYPE_LONGLONG;
+    put_param[0].buffer         = &stmt_param_id;
+    put_param[0].is_unsigned    = true;
+
+    put_param[1].buffer_type    = MYSQL_TYPE_STRING;
+    put_param[1].buffer         = &stmt_param_name;
+    put_param[1].buffer_length  = sizeof(stmt_param_name);
+    put_param[1].length         = &stmt_param_name_len;
+
+    put_param[2].buffer_type    = MYSQL_TYPE_LONGLONG;
+    put_param[2].buffer         = &stmt_param_did;
+    put_param[2].is_unsigned    = true;
+
+    put_param[3].buffer_type    = MYSQL_TYPE_LONGLONG;
+    put_param[3].buffer         = &stmt_param_dev;
+    put_param[3].is_unsigned    = true;
+
+    put_param[4].buffer_type    = MYSQL_TYPE_LONGLONG;
+    put_param[4].buffer         = &stmt_param_ino;
+    put_param[4].is_unsigned    = true;
+
+    EC_ZERO_LOG( mysql_stmt_prepare(db->cnid_put_stmt, sql, strlen(sql)) );
+    EC_ZERO_LOG( mysql_stmt_bind_param(db->cnid_put_stmt, put_param) );
+
+EC_CLEANUP:
+    if (sql)
+        free(sql);
+    EC_EXIT;
+}
+
+static int init_prepared_stmt(CNID_mysql_private *db)
+{
+    EC_INIT;
+
+    EC_ZERO( init_prepared_stmt_lookup(db) );
+    EC_ZERO( init_prepared_stmt_add(db) );
+    EC_ZERO( init_prepared_stmt_put(db) );
+
+EC_CLEANUP:
+    EC_EXIT;
+}
+
+static void close_prepared_stmt(CNID_mysql_private *db)
+{
+    mysql_stmt_close(db->cnid_lookup_stmt);
+    mysql_stmt_close(db->cnid_add_stmt);
+    mysql_stmt_close(db->cnid_put_stmt);
+}
+
+static int cnid_mysql_execute(MYSQL *con, char *fmt, ...)
+{
+    char *sql = NULL;
+    va_list ap;
+    int rv;
+
+    va_start(ap, fmt);
+    if (vasprintf(&sql, fmt, ap) == -1)
+        return -1;
+    va_end(ap);
+
+    LOG(log_maxdebug, logtype_cnid, "SQL: %s", sql);
+
+    rv = mysql_query(con, sql);
+
+    if (rv) {
+        LOG(log_info, logtype_cnid, "MySQL query \"%s\", error: %s", sql, mysql_error(con));
+        errno = CNID_ERR_DB;
+    }
+    free(sql);
+    return rv;
+}
+
+int cnid_mysql_delete(struct _cnid_db *cdb, const cnid_t id)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+
+    if (!cdb || !(db = cdb->_private) || !id) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_delete: Parameter error");
+        errno = CNID_ERR_PARAM;
+        EC_FAIL;
+    }
+
+    LOG(log_debug, logtype_cnid, "cnid_mysql_delete(%" PRIu32 "): BEGIN", ntohl(id));
+    
+    EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
+                                "DELETE FROM %s WHERE Id=%" PRIu32,
+                                db->cnid_mysql_voluuid_str,
+                                ntohl(id)) );
+
+    LOG(log_debug, logtype_cnid, "cnid_mysql_delete(%" PRIu32 "): END", ntohl(id));
+
+EC_CLEANUP:
+    EC_EXIT;
+}
+
+void cnid_mysql_close(struct _cnid_db *cdb)
+{
+    CNID_mysql_private *db;
+
+    if (!cdb) {
+        LOG(log_error, logtype_cnid, "cnid_close called with NULL argument !");
+        return;
+    }
+
+    if ((db = cdb->_private) != NULL) {
+        LOG(log_debug, logtype_cnid, "closing database connection for volume '%s'", db->cnid_mysql_volname);
+
+        free(db->cnid_mysql_voluuid_str);
+
+        close_prepared_stmt(db);
+
+        if (db->cnid_mysql_con)
+            mysql_close(db->cnid_mysql_con);
+        free(db);
+    }
+
+    free(cdb->volpath);
+    free(cdb);
+
+    return;
+}
+
+int cnid_mysql_update(struct _cnid_db *cdb,
+                      cnid_t id,
+                      const struct stat *st,
+                      cnid_t did,
+                      const char *name,
+                      size_t len)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+    cnid_t update_id;
+
+    if (!cdb || !(db = cdb->_private) || !id || !st || !name) {
+        LOG(log_error, logtype_cnid, "cnid_update: Parameter error");
+        errno = CNID_ERR_PARAM;
+        EC_FAIL;
+    }
+
+    if (len > MAXPATHLEN) {
+        LOG(log_error, logtype_cnid, "cnid_update: Path name is too long");
+        errno = CNID_ERR_PATH;
+        EC_FAIL;
+    }
+
+    uint64_t dev = st->st_dev;
+    uint64_t ino = st->st_ino;
+
+    do {
+        EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
+                                    "DELETE FROM %s WHERE Id=%" PRIu32,
+                                    db->cnid_mysql_voluuid_str,
+                                    ntohl(id)) );
+        EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
+                                    "DELETE FROM %s WHERE Did=%" PRIu32 " AND Name='%s'",
+                                    db->cnid_mysql_voluuid_str, ntohl(did), name) );
+        EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
+                                    "DELETE FROM %s WHERE DevNo=%" PRIu64 " AND InodeNo=%" PRIu64,
+                                    db->cnid_mysql_voluuid_str, dev, ino) );
+
+        stmt_param_id = ntohl(id);
+        strncpy(stmt_param_name, name, sizeof(stmt_param_name));
+        stmt_param_name_len = len;
+        stmt_param_did = ntohl(did);
+        stmt_param_dev = dev;
+        stmt_param_ino = ino;
+
+        if (mysql_stmt_execute(db->cnid_put_stmt)) {
+            switch (mysql_stmt_errno(db->cnid_put_stmt)) {
+            case ER_DUP_ENTRY:
+                /*
+                 * Race condition:
+                 * between deletion and insert another process may have inserted
+                 * this entry.
+                 */
+                continue;
+            case CR_SERVER_LOST:
+                close_prepared_stmt(db);
+                EC_ZERO( init_prepared_stmt(db) );
+                continue;
+            default:
+                EC_FAIL;
+            }
+        }
+        update_id = mysql_stmt_insert_id(db->cnid_put_stmt);
+    } while (update_id != ntohl(id));
+
+EC_CLEANUP:
+    EC_EXIT;
+}
+
+cnid_t cnid_mysql_lookup(struct _cnid_db *cdb,
+                         const struct stat *st,
+                         cnid_t did,
+                         const char *name,
+                         size_t len)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+    cnid_t id = CNID_INVALID;
+    bool have_result = false;
+
+    if (!cdb || !(db = cdb->_private) || !st || !name) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_lookup: Parameter error");
+        errno = CNID_ERR_PARAM;
+        EC_FAIL;
+    }
+
+    if (len > MAXPATHLEN) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_lookup: Path name is too long");
+        errno = CNID_ERR_PATH;
+        EC_FAIL;
+    }
+
+    uint64_t dev = st->st_dev;
+    uint64_t ino = st->st_ino;
+    cnid_t hint = db->cnid_mysql_hint;
+
+    LOG(log_maxdebug, logtype_cnid, "cnid_mysql_lookup(did: %" PRIu32 ", name: \"%s\", hint: %" PRIu32 "): START",
+        ntohl(did), name, ntohl(hint));
+
+    strncpy(stmt_param_name, name, sizeof(stmt_param_name));
+    stmt_param_name_len = len;
+    stmt_param_did = ntohl(did);
+    stmt_param_dev = dev;
+    stmt_param_ino = ino;
+
+exec_stmt:
+    if (mysql_stmt_execute(db->cnid_lookup_stmt)) {
+        switch (mysql_stmt_errno(db->cnid_lookup_stmt)) {
+        case CR_SERVER_LOST:
+            close_prepared_stmt(db);
+            EC_ZERO( init_prepared_stmt(db) );
+            goto exec_stmt;
+        default:
+            EC_FAIL;
+        }
+    }
+    EC_ZERO_LOG( mysql_stmt_store_result(db->cnid_lookup_stmt) );
+    have_result = true;
+    EC_ZERO_LOG( mysql_stmt_bind_result(db->cnid_lookup_stmt, lookup_result) );
+
+    uint64_t retdev, retino;
+    cnid_t retid, retdid;
+    char *retname;
+
+    switch (mysql_stmt_num_rows(db->cnid_lookup_stmt)) {
+
+    case 0:
+        /* not found */
+        LOG(log_debug, logtype_cnid, "cnid_mysql_lookup: name: '%s', did: %u is not in the CNID database", 
+            name, ntohl(did));
+        errno = CNID_DBD_RES_NOTFOUND;
+        EC_FAIL;
+
+    case 1:
+        /* either both OR clauses matched the same id or only one matched, handled below */
+        EC_ZERO( mysql_stmt_fetch(db->cnid_lookup_stmt) );
+        break;
+
+    case 2:
+        /* a mismatch, delete both and return not found */
+        while (mysql_stmt_fetch(db->cnid_lookup_stmt) == 0) {
+            if (cnid_mysql_delete(cdb, htonl((cnid_t)lookup_result_id))) {
+                LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+                errno = CNID_ERR_DB;
+                EC_FAIL;
+            }
+        }
+        errno = CNID_DBD_RES_NOTFOUND;
+        EC_FAIL;
+
+    default:
+        errno = CNID_ERR_DB;
+        EC_FAIL;
+    }
+
+    retid = htonl(lookup_result_id);
+    retdid = htonl(lookup_result_did);
+    retname = lookup_result_name;
+    retdev = lookup_result_dev;
+    retino = lookup_result_ino;
+
+    if (retdid != did || STRCMP(retname, !=, name)) {
+        LOG(log_debug, logtype_cnid,
+            "cnid_mysql_lookup(CNID hint: %" PRIu32 ", DID: %" PRIu32 ", name: \"%s\"): server side mv oder reused inode",
+            ntohl(hint), ntohl(did), name);
+        if (hint != retid) {
+            if (cnid_mysql_delete(cdb, retid) != 0) {
+                LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+                errno = CNID_ERR_DB;
+                EC_FAIL;
+            }
+            errno = CNID_DBD_RES_NOTFOUND;
+            EC_FAIL;
+        }
+        LOG(log_debug, logtype_cnid, "cnid_mysql_lookup: server side mv, got hint, updating");
+        if (cnid_mysql_update(cdb, retid, st, did, name, len) != 0) {
+            LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+            errno = CNID_ERR_DB;
+            EC_FAIL;
+        }
+        id = retid;
+    } else if (retdev != dev || retino != ino) {
+        LOG(log_debug, logtype_cnid, "cnid_mysql_lookup(DID:%u, name: \"%s\"): changed dev/ino",
+            ntohl(did), name);
+        if (cnid_mysql_delete(cdb, retid) != 0) {
+            LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+            errno = CNID_ERR_DB;
+            EC_FAIL;
+        }
+        errno = CNID_DBD_RES_NOTFOUND;
+        EC_FAIL;
+    } else {
+        /* everythings good */
+        id = retid;
+    }
+
+EC_CLEANUP:
+    LOG(log_debug, logtype_cnid, "cnid_mysql_lookup: id: %" PRIu32, ntohl(id));
+    if (have_result)
+        mysql_stmt_free_result(db->cnid_lookup_stmt);
+    if (ret != 0)
+        id = CNID_INVALID;
+    return id;
+}
+
+cnid_t cnid_mysql_add(struct _cnid_db *cdb,
+                      const struct stat *st,
+                      cnid_t did,
+                      const char *name,
+                      size_t len,
+                      cnid_t hint)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+    cnid_t id = CNID_INVALID;
+    MYSQL_RES *result = NULL;
+    MYSQL_STMT *stmt;
+    my_ulonglong lastid;
+
+    if (!cdb || !(db = cdb->_private) || !st || !name) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_add: Parameter error");
+        errno = CNID_ERR_PARAM;
+        EC_FAIL;
+    }
+
+    if (len > MAXPATHLEN) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_add: Path name is too long");
+        errno = CNID_ERR_PATH;
+        EC_FAIL;
+    }
+
+    uint64_t dev = st->st_dev;
+    uint64_t ino = st->st_ino;
+    db->cnid_mysql_hint = hint;
+
+    LOG(log_maxdebug, logtype_cnid, "cnid_mysql_add(did: %" PRIu32 ", name: \"%s\", hint: %" PRIu32 "): START",
+        ntohl(did), name, ntohl(hint));
+
+    do {
+        if ((id = cnid_mysql_lookup(cdb, st, did, name, len)) == CNID_INVALID) {
+            if (errno == CNID_ERR_DB)
+                EC_FAIL;
+            /*
+             * If the CNID set overflowed before (CNID_MYSQL_FLAG_DEPLETED)
+             * ignore the CNID "hint" taken from the AppleDouble file
+             */
+            if (!db->cnid_mysql_hint || (db->cnid_mysql_flags & CNID_MYSQL_FLAG_DEPLETED)) {
+                stmt = db->cnid_add_stmt;
+            } else {
+                stmt = db->cnid_put_stmt;
+                stmt_param_id = ntohl(db->cnid_mysql_hint);
+            }
+            strncpy(stmt_param_name, name, sizeof(stmt_param_name));
+            stmt_param_name_len = len;
+            stmt_param_did = ntohl(did);
+            stmt_param_dev = dev;
+            stmt_param_ino = ino;
+
+            if (mysql_stmt_execute(stmt)) {
+                switch (mysql_stmt_errno(stmt)) {
+                case ER_DUP_ENTRY:
+                    break;
+                case CR_SERVER_LOST:
+                    close_prepared_stmt(db);
+                    EC_ZERO( init_prepared_stmt(db) );
+                    continue;
+                default:
+                    EC_FAIL;
+                }
+                /*
+                 * Race condition:
+                 * between lookup and insert another process may have inserted
+                 * this entry.
+                 */
+                if (db->cnid_mysql_hint)
+                    db->cnid_mysql_hint = CNID_INVALID;
+                continue;
+            }
+
+            lastid = mysql_stmt_insert_id(stmt);
+
+            if (lastid > 0xffffffff) {
+                /* CNID set ist depleted, restart from scratch */
+                EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
+                                            "START TRANSACTION;"
+                                            "UPDATE volumes SET Depleted=1 WHERE VolUUID='%s';"
+                                            "TRUNCATE TABLE %s;"
+                                            "ALTER TABLE %s AUTO_INCREMENT = 17;" 
+                                            "COMMIT;",
+                                            db->cnid_mysql_voluuid_str,
+                                            db->cnid_mysql_voluuid_str,
+                                            db->cnid_mysql_voluuid_str) );
+                db->cnid_mysql_flags |= CNID_MYSQL_FLAG_DEPLETED;
+                hint = CNID_INVALID;
+                do {
+                    result = mysql_store_result(db->cnid_mysql_con);
+                    if (result)
+                        mysql_free_result(result);
+                } while (mysql_next_result(db->cnid_mysql_con) == 0);
+                continue;
+            }
+
+            /* Finally assign our result */
+            id = htonl((uint32_t)lastid);
+        }
+    } while (id == CNID_INVALID);
+
+EC_CLEANUP:
+    LOG(log_debug, logtype_cnid, "cnid_mysql_add: id: %" PRIu32, ntohl(id));
+
+    if (result)
+        mysql_free_result(result);
+    return id;
+}
+
+cnid_t cnid_mysql_get(struct _cnid_db *cdb, cnid_t did, const char *name, size_t len)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+    cnid_t id = CNID_INVALID;
+    MYSQL_RES *result = NULL;
+    MYSQL_ROW row;
+
+    if (!cdb || !(db = cdb->_private) || !name) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_get: Parameter error");
+        errno = CNID_ERR_PARAM;
+        EC_FAIL;
+    }
+
+    if (len > MAXPATHLEN) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_get: name is too long");
+        errno = CNID_ERR_PATH;
+        return CNID_INVALID;
+    }
+
+    LOG(log_debug, logtype_cnid, "cnid_mysql_get(did: %" PRIu32 ", name: \"%s\"): START",
+        ntohl(did),name);
+
+    EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
+                                "SELECT Id FROM %s "
+                                "WHERE Name='%s' AND Did=%" PRIu32,
+                                db->cnid_mysql_voluuid_str,
+                                name,
+                                ntohl(did)) );
+
+    if ((result = mysql_store_result(db->cnid_mysql_con)) == NULL) {
+        LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+        errno = CNID_ERR_DB;
+        EC_FAIL;
+    }
+
+    if (mysql_num_rows(result)) {
+        row = mysql_fetch_row(result);
+        id = htonl(atoi(row[0]));
+    }
+
+EC_CLEANUP:
+    LOG(log_debug, logtype_cnid, "cnid_mysql_get: id: %" PRIu32, ntohl(id));
+
+    if (result)
+        mysql_free_result(result);
+
+    return id;
+}
+
+char *cnid_mysql_resolve(struct _cnid_db *cdb, cnid_t *id, void *buffer, size_t len)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+    MYSQL_RES *result = NULL;
+    MYSQL_ROW row;
+
+    if (!cdb || !(db = cdb->_private)) {
+        LOG(log_error, logtype_cnid, "cnid_mysql_get: Parameter error");
+        errno = CNID_ERR_PARAM;
+        EC_FAIL;
+    }
+
+    EC_NEG1( cnid_mysql_execute(
+                 db->cnid_mysql_con,
+                 "SELECT Did,Name FROM %s WHERE Id=%" PRIu32,
+                 db->cnid_mysql_voluuid_str, ntohl(*id)) );
+
+    EC_NULL( result = mysql_store_result(db->cnid_mysql_con) );
+
+    if (mysql_num_rows(result) != 1)
+        EC_FAIL;
+
+    row = mysql_fetch_row(result);
+
+    *id = htonl(atoi(row[0]));
+    strncpy(buffer, row[1], len);
+
+EC_CLEANUP:             
+    if (result)
+        mysql_free_result(result);
+
+    if (ret != 0) {
+        *id = CNID_INVALID;
+        return NULL;
+    }
+    return buffer;
+}
+
+/**
+ * Caller passes buffer where we will store the db stamp
+ **/
+int cnid_mysql_getstamp(struct _cnid_db *cdb, void *buffer, const size_t len)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+    MYSQL_RES *result = NULL;
+    MYSQL_ROW row;
+
+    if (!cdb || !(db = cdb->_private)) {
+        LOG(log_error, logtype_cnid, "cnid_find: Parameter error");
+        errno = CNID_ERR_PARAM;
+        return CNID_INVALID;
+    }
+
+    if (!buffer)
+        EC_EXIT_STATUS(0);
+
+    if (cnid_mysql_execute(db->cnid_mysql_con,
+                           "SELECT Stamp FROM volumes WHERE VolPath='%s'",
+                           db->cnid_mysql_volname)) {
+        if (mysql_errno(db->cnid_mysql_con) != ER_DUP_ENTRY) {
+            LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+            EC_FAIL;
+        }
+    }
+
+    if ((result = mysql_store_result(db->cnid_mysql_con)) == NULL) {
+        LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+        errno = CNID_ERR_DB;
+        EC_FAIL;
+    }
+    if (!mysql_num_rows(result)) {
+        LOG(log_error, logtype_cnid, "Can't get DB stamp for volumes \"%s\"", db->cnid_mysql_volname);
+        EC_FAIL;
+    }
+    row = mysql_fetch_row(result);
+    memcpy(buffer, row[0], len);
+
+EC_CLEANUP:
+    if (result)
+        mysql_free_result(result);
+    EC_EXIT;
+}
+
+
+int cnid_mysql_find(struct _cnid_db *cdb, const char *name, size_t namelen, void *buffer, size_t buflen)
+{
+    LOG(log_error, logtype_cnid,
+        "cnid_mysql_find(\"%s\"): not supported with MySQL CNID backend", name);
+    return -1;
+}
+
+cnid_t cnid_mysql_rebuild_add(struct _cnid_db *cdb, const struct stat *st,
+                              cnid_t did, const char *name, size_t len, cnid_t hint)
+{
+    LOG(log_error, logtype_cnid,
+        "cnid_mysql_rebuild_add(\"%s\"): not supported with MySQL CNID backend", name);
+    return CNID_INVALID;
+}
+
+int cnid_mysql_wipe(struct _cnid_db *cdb)
+{
+    EC_INIT;
+    CNID_mysql_private *db;
+    MYSQL_RES *result = NULL;
+
+    if (!cdb || !(db = cdb->_private)) {
+        LOG(log_error, logtype_cnid, "cnid_wipe: Parameter error");
+        errno = CNID_ERR_PARAM;
+        return -1;
+    }
+
+    LOG(log_debug, logtype_cnid, "cnid_dbd_wipe");
+
+    EC_NEG1( cnid_mysql_execute(
+                 db->cnid_mysql_con,
+                 "START TRANSACTION;"
+                 "UPDATE volumes SET Depleted=0 WHERE VolUUID='%s';"
+                 "TRUNCATE TABLE %s;"
+                 "ALTER TABLE %s AUTO_INCREMENT = 17;"
+                 "COMMIT;",
+                 db->cnid_mysql_voluuid_str,
+                 db->cnid_mysql_voluuid_str,
+                 db->cnid_mysql_voluuid_str) );
+
+    do {
+        result = mysql_store_result(db->cnid_mysql_con);
+        if (result)
+            mysql_free_result(result);
+    } while (mysql_next_result(db->cnid_mysql_con) == 0);
+
+EC_CLEANUP:
+    EC_EXIT;
+}
+
+static struct _cnid_db *cnid_mysql_new(const char *volpath)
+{
+    struct _cnid_db *cdb;
+
+    if ((cdb = (struct _cnid_db *)calloc(1, sizeof(struct _cnid_db))) == NULL)
+        return NULL;
+
+    if ((cdb->volpath = strdup(volpath)) == NULL) {
+        free(cdb);
+        return NULL;
+    }
+
+    cdb->flags = CNID_FLAG_PERSISTENT | CNID_FLAG_LAZY_INIT;
+    cdb->cnid_add = cnid_mysql_add;
+    cdb->cnid_delete = cnid_mysql_delete;
+    cdb->cnid_get = cnid_mysql_get;
+    cdb->cnid_lookup = cnid_mysql_lookup;
+    cdb->cnid_find = cnid_mysql_find;
+    cdb->cnid_nextid = NULL;
+    cdb->cnid_resolve = cnid_mysql_resolve;
+    cdb->cnid_getstamp = cnid_mysql_getstamp;
+    cdb->cnid_update = cnid_mysql_update;
+    cdb->cnid_rebuild_add = cnid_mysql_rebuild_add;
+    cdb->cnid_close = cnid_mysql_close;
+    cdb->cnid_wipe = cnid_mysql_wipe;
+    return cdb;
+}
+
+static const char *printuuid(const unsigned char *uuid) {
+    static char uuidstring[64];
+    const char *uuidmask;
+    int i = 0;
+    unsigned char c;
+
+    while ((c = *uuid)) {
+        uuid++;
+        sprintf(uuidstring + i, "%02X", c);
+        i += 2;
+    }
+    uuidstring[i] = 0;
+    return uuidstring;
+}
+
+/* ---------------------- */
+struct _cnid_db *cnid_mysql_open(struct cnid_open_args *args)
+{
+    EC_INIT;
+    CNID_mysql_private *db = NULL;
+    struct _cnid_db *cdb = NULL;
+    MYSQL_RES *result = NULL;
+    MYSQL_ROW row;
+
+    EC_NULL( cdb = cnid_mysql_new(args->dir) );
+    EC_NULL( db = (CNID_mysql_private *)calloc(1, sizeof(CNID_mysql_private)) );
+    cdb->_private = db;
+
+    db->cnid_mysql_volname = strdup(args->dir); /* db_dir contains the volume name */
+    db->cnid_mysql_magic = CNID_DB_MAGIC;
+    db->cnid_mysql_obj = args->obj;
+    memcpy(db->cnid_mysql_voluuid, args->voluuid, sizeof(atalk_uuid_t));
+    db->cnid_mysql_voluuid_str = strdup(printuuid(db->cnid_mysql_voluuid));
+
+    /* Initialize and connect to MySQL server */
+    EC_NULL( db->cnid_mysql_con = mysql_init(NULL) );
+    my_bool my_recon = true;
+    EC_ZERO( mysql_options(db->cnid_mysql_con, MYSQL_OPT_RECONNECT, &my_recon) );
+    int my_timeout = 600;
+    EC_ZERO( mysql_options(db->cnid_mysql_con, MYSQL_OPT_CONNECT_TIMEOUT, &my_timeout) );
+
+    const AFPObj *obj = db->cnid_mysql_obj;
+
+    EC_NULL( mysql_real_connect(db->cnid_mysql_con,
+                                obj->options.cnid_mysql_host,
+                                obj->options.cnid_mysql_user,
+                                obj->options.cnid_mysql_pw,
+                                obj->options.cnid_mysql_db,
+                                0, NULL, CLIENT_MULTI_STATEMENTS));
+
+    /* Add volume to volume table */
+    if (cnid_mysql_execute(db->cnid_mysql_con,
+                           "CREATE TABLE IF NOT EXISTS volumes"
+                           "(VolUUID CHAR(32) PRIMARY KEY,VolPath TEXT(4096),Stamp BINARY(8),Depleted INT, INDEX(VolPath(64)))")) {
+        LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+        EC_FAIL;
+    }
+    time_t now = time(NULL);
+    char stamp[8];
+    memset(stamp, 0, 8);
+    memcpy(stamp, &now, sizeof(time_t));
+    char blob[16+1];
+    mysql_real_escape_string(db->cnid_mysql_con, blob, stamp, 8);
+
+    if (cnid_mysql_execute(db->cnid_mysql_con,
+                           "INSERT INTO volumes (VolUUID,Volpath,Stamp,Depleted) "
+                           "VALUES('%s','%s','%s',0)",
+                           db->cnid_mysql_voluuid_str,
+                           db->cnid_mysql_volname,
+                           blob)) {
+        if (mysql_errno(db->cnid_mysql_con) != ER_DUP_ENTRY) {
+            LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+            EC_FAIL;
+        }
+    }
+
+    /*
+     * Check whether CNID set overflowed before.
+     * If that's the case, in cnid_mysql_add() we'll ignore the CNID "hint" taken from the
+     * AppleDouble file.
+     */
+    if (cnid_mysql_execute(db->cnid_mysql_con,
+                           "SELECT Depleted FROM volumes WHERE VolUUID='%s'",
+                           db->cnid_mysql_voluuid_str)) {
+        LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+        EC_FAIL;
+    }
+    if ((result = mysql_store_result(db->cnid_mysql_con)) == NULL) {
+        LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+        errno = CNID_ERR_DB;
+        EC_FAIL;
+    }
+    if (mysql_num_rows(result)) {
+        row = mysql_fetch_row(result);
+        int depleted = atoi(row[0]);
+        if (depleted)
+            db->cnid_mysql_flags |= CNID_MYSQL_FLAG_DEPLETED;
+    }
+    mysql_free_result(result);
+    result = NULL;
+
+    /* Create volume table */
+    if (cnid_mysql_execute(db->cnid_mysql_con,
+                           "CREATE TABLE IF NOT EXISTS %s"
+                           "(Id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,"
+                           "Name VARCHAR(255) NOT NULL,"
+                           "Did INT UNSIGNED NOT NULL,"
+                           "DevNo BIGINT UNSIGNED NOT NULL,"
+                           "InodeNo BIGINT UNSIGNED NOT NULL,"
+                           "UNIQUE DidName(Did, Name), UNIQUE DevIno(DevNo, InodeNo)) "
+                           "AUTO_INCREMENT=17",
+                           db->cnid_mysql_voluuid_str)) {
+        LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
+        EC_FAIL;
+    }
+
+    EC_ZERO( init_prepared_stmt(db) );
+
+    LOG(log_debug, logtype_cnid, "Finished initializing MySQL CNID module for volume '%s'",
+        db->cnid_mysql_volname);
+
+EC_CLEANUP:
+    if (result)
+        mysql_free_result(result);
+    if (ret != 0) {
+        if (cdb) {
+            if (cdb->volpath != NULL) {
+                free(cdb->volpath);
+            }
+            free(cdb);
+        }
+        cdb = NULL;
+        if (db)
+            free(db);
+    }
+    return cdb;
+}
+
+struct _cnid_module cnid_mysql_module = {
+    "mysql",
+    {NULL, NULL},
+    cnid_mysql_open,
+    0
+};