]> arthur.barton.de Git - netatalk.git/blob - libatalk/cnid/mysql/cnid_mysql.c
Spotlight: use async Tracker SPARQL API
[netatalk.git] / libatalk / cnid / mysql / cnid_mysql.c
1 /*
2  * Copyright (C) Ralph Boehme 2013
3  * All Rights Reserved.  See COPYING.
4  */
5
6 #ifdef HAVE_CONFIG_H
7 #include "config.h"
8 #endif /* HAVE_CONFIG_H */
9
10 #undef _FORTIFY_SOURCE
11
12 #include <stdlib.h>
13 #include <sys/stat.h>
14 #include <sys/uio.h>
15 #include <sys/time.h>
16 #include <sys/un.h>
17 #include <sys/socket.h>
18 #include <sys/param.h>
19 #include <errno.h>
20 #include <netinet/in.h>
21 #include <net/if.h>
22 #include <netinet/tcp.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25 #include <errno.h>
26 #include <netdb.h>
27 #include <time.h>
28 #include <arpa/inet.h>
29
30 #include <mysql.h>
31 #include <mysqld_error.h>
32 #include <errmsg.h>
33
34 #include <atalk/logger.h>
35 #include <atalk/adouble.h>
36 #include <atalk/util.h>
37 #include <atalk/cnid_mysql_private.h>
38 #include <atalk/cnid_bdb_private.h>
39 #include <atalk/errchk.h>
40 #include <atalk/globals.h>
41 #include <atalk/volume.h>
42
43
44 static MYSQL_BIND lookup_param[4], lookup_result[5];
45 static MYSQL_BIND add_param[4], put_param[5];
46
47 /*
48  * Prepared statement parameters
49  */
50 static char               stmt_param_name[MAXPATHLEN];
51 static unsigned long      stmt_param_name_len;
52 static unsigned long long stmt_param_id;
53 static unsigned long long stmt_param_did;
54 static unsigned long long stmt_param_dev;
55 static unsigned long long stmt_param_ino;
56
57 /*
58  * lookup result parameters
59  */
60 static unsigned long long lookup_result_id;
61 static unsigned long long lookup_result_did;
62 static char               lookup_result_name[MAXPATHLEN];
63 static unsigned long      lookup_result_name_len;
64 static unsigned long long lookup_result_dev;
65 static unsigned long long lookup_result_ino;
66
67 static int init_prepared_stmt_lookup(CNID_mysql_private *db)
68 {
69     EC_INIT;
70     char *sql = NULL;
71
72     lookup_param[0].buffer_type    = MYSQL_TYPE_STRING;
73     lookup_param[0].buffer         = &stmt_param_name;
74     lookup_param[0].buffer_length  = sizeof(stmt_param_name);
75     lookup_param[0].length         = &stmt_param_name_len;
76
77     lookup_param[1].buffer_type    = MYSQL_TYPE_LONGLONG;
78     lookup_param[1].buffer         = &stmt_param_did;
79     lookup_param[1].is_unsigned    = true;
80
81     lookup_param[2].buffer_type    = MYSQL_TYPE_LONGLONG;
82     lookup_param[2].buffer         = &stmt_param_dev;
83     lookup_param[2].is_unsigned    = true;
84
85     lookup_param[3].buffer_type    = MYSQL_TYPE_LONGLONG;
86     lookup_param[3].buffer         = &stmt_param_ino;
87     lookup_param[3].is_unsigned    = true;
88
89     lookup_result[0].buffer_type   = MYSQL_TYPE_LONGLONG;
90     lookup_result[0].buffer        = &lookup_result_id;
91     lookup_result[0].is_unsigned   = true;
92
93     lookup_result[1].buffer_type   = MYSQL_TYPE_LONGLONG;
94     lookup_result[1].buffer        = &lookup_result_did;
95     lookup_result[1].is_unsigned   = true;
96
97     lookup_result[2].buffer_type   = MYSQL_TYPE_STRING;
98     lookup_result[2].buffer        = &lookup_result_name;
99     lookup_result[2].buffer_length = sizeof(lookup_result_name);
100     lookup_result[2].length        = &lookup_result_name_len;
101
102     lookup_result[3].buffer_type   = MYSQL_TYPE_LONGLONG;
103     lookup_result[3].buffer        = &lookup_result_dev;
104     lookup_result[3].is_unsigned   = true;
105
106     lookup_result[4].buffer_type   = MYSQL_TYPE_LONGLONG;
107     lookup_result[4].buffer        = &lookup_result_ino;
108     lookup_result[4].is_unsigned   = true;
109
110     EC_NULL( db->cnid_lookup_stmt = mysql_stmt_init(db->cnid_mysql_con) );
111     EC_NEG1( asprintf(&sql,
112                       "SELECT Id,Did,Name,DevNo,InodeNo FROM %s "
113                       "WHERE (Name=? AND Did=?) OR (DevNo=? AND InodeNo=?)",
114                       db->cnid_mysql_voluuid_str) );
115     EC_ZERO_LOG( mysql_stmt_prepare(db->cnid_lookup_stmt, sql, strlen(sql)) );
116     EC_ZERO_LOG( mysql_stmt_bind_param(db->cnid_lookup_stmt, lookup_param) );
117
118 EC_CLEANUP:
119     if (sql)
120         free(sql);
121     EC_EXIT;
122 }
123
124 static int init_prepared_stmt_add(CNID_mysql_private *db)
125 {
126     EC_INIT;
127     char *sql = NULL;
128
129     EC_NULL( db->cnid_add_stmt = mysql_stmt_init(db->cnid_mysql_con) );
130     EC_NEG1( asprintf(&sql,
131                       "INSERT INTO %s (Name,Did,DevNo,InodeNo) VALUES(?,?,?,?)",
132                       db->cnid_mysql_voluuid_str) );
133
134     add_param[0].buffer_type    = MYSQL_TYPE_STRING;
135     add_param[0].buffer         = &stmt_param_name;
136     add_param[0].buffer_length  = sizeof(stmt_param_name);
137     add_param[0].length         = &stmt_param_name_len;
138
139     add_param[1].buffer_type    = MYSQL_TYPE_LONGLONG;
140     add_param[1].buffer         = &stmt_param_did;
141     add_param[1].is_unsigned    = true;
142
143     add_param[2].buffer_type    = MYSQL_TYPE_LONGLONG;
144     add_param[2].buffer         = &stmt_param_dev;
145     add_param[2].is_unsigned    = true;
146
147     add_param[3].buffer_type    = MYSQL_TYPE_LONGLONG;
148     add_param[3].buffer         = &stmt_param_ino;
149     add_param[3].is_unsigned    = true;
150
151     EC_ZERO_LOG( mysql_stmt_prepare(db->cnid_add_stmt, sql, strlen(sql)) );
152     EC_ZERO_LOG( mysql_stmt_bind_param(db->cnid_add_stmt, add_param) );
153
154 EC_CLEANUP:
155     if (sql)
156         free(sql);
157     EC_EXIT;
158 }
159
160 static int init_prepared_stmt_put(CNID_mysql_private *db)
161 {
162     EC_INIT;
163     char *sql = NULL;
164
165     EC_NULL( db->cnid_put_stmt = mysql_stmt_init(db->cnid_mysql_con) );
166     EC_NEG1( asprintf(&sql,
167                       "INSERT INTO %s (Id,Name,Did,DevNo,InodeNo) VALUES(?,?,?,?,?)",
168                       db->cnid_mysql_voluuid_str) );
169
170     put_param[0].buffer_type    = MYSQL_TYPE_LONGLONG;
171     put_param[0].buffer         = &stmt_param_id;
172     put_param[0].is_unsigned    = true;
173
174     put_param[1].buffer_type    = MYSQL_TYPE_STRING;
175     put_param[1].buffer         = &stmt_param_name;
176     put_param[1].buffer_length  = sizeof(stmt_param_name);
177     put_param[1].length         = &stmt_param_name_len;
178
179     put_param[2].buffer_type    = MYSQL_TYPE_LONGLONG;
180     put_param[2].buffer         = &stmt_param_did;
181     put_param[2].is_unsigned    = true;
182
183     put_param[3].buffer_type    = MYSQL_TYPE_LONGLONG;
184     put_param[3].buffer         = &stmt_param_dev;
185     put_param[3].is_unsigned    = true;
186
187     put_param[4].buffer_type    = MYSQL_TYPE_LONGLONG;
188     put_param[4].buffer         = &stmt_param_ino;
189     put_param[4].is_unsigned    = true;
190
191     EC_ZERO_LOG( mysql_stmt_prepare(db->cnid_put_stmt, sql, strlen(sql)) );
192     EC_ZERO_LOG( mysql_stmt_bind_param(db->cnid_put_stmt, put_param) );
193
194 EC_CLEANUP:
195     if (sql)
196         free(sql);
197     EC_EXIT;
198 }
199
200 static int init_prepared_stmt(CNID_mysql_private *db)
201 {
202     EC_INIT;
203
204     EC_ZERO( init_prepared_stmt_lookup(db) );
205     EC_ZERO( init_prepared_stmt_add(db) );
206     EC_ZERO( init_prepared_stmt_put(db) );
207
208 EC_CLEANUP:
209     EC_EXIT;
210 }
211
212 static void close_prepared_stmt(CNID_mysql_private *db)
213 {
214     mysql_stmt_close(db->cnid_lookup_stmt);
215     mysql_stmt_close(db->cnid_add_stmt);
216     mysql_stmt_close(db->cnid_put_stmt);
217 }
218
219 static int cnid_mysql_execute(MYSQL *con, char *fmt, ...)
220 {
221     char *sql = NULL;
222     va_list ap;
223     int rv;
224
225     va_start(ap, fmt);
226     if (vasprintf(&sql, fmt, ap) == -1)
227         return -1;
228     va_end(ap);
229
230     LOG(log_maxdebug, logtype_cnid, "SQL: %s", sql);
231
232     rv = mysql_query(con, sql);
233
234     if (rv) {
235         LOG(log_info, logtype_cnid, "MySQL query \"%s\", error: %s", sql, mysql_error(con));
236         errno = CNID_ERR_DB;
237     }
238     free(sql);
239     return rv;
240 }
241
242 int cnid_mysql_delete(struct _cnid_db *cdb, const cnid_t id)
243 {
244     EC_INIT;
245     CNID_mysql_private *db;
246
247     if (!cdb || !(db = cdb->cnid_db_private) || !id) {
248         LOG(log_error, logtype_cnid, "cnid_mysql_delete: Parameter error");
249         errno = CNID_ERR_PARAM;
250         EC_FAIL;
251     }
252
253     LOG(log_debug, logtype_cnid, "cnid_mysql_delete(%" PRIu32 "): BEGIN", ntohl(id));
254     
255     EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
256                                 "DELETE FROM %s WHERE Id=%" PRIu32,
257                                 db->cnid_mysql_voluuid_str,
258                                 ntohl(id)) );
259
260     LOG(log_debug, logtype_cnid, "cnid_mysql_delete(%" PRIu32 "): END", ntohl(id));
261
262 EC_CLEANUP:
263     EC_EXIT;
264 }
265
266 void cnid_mysql_close(struct _cnid_db *cdb)
267 {
268     CNID_mysql_private *db;
269
270     if (!cdb) {
271         LOG(log_error, logtype_cnid, "cnid_close called with NULL argument !");
272         return;
273     }
274
275     if ((db = cdb->cnid_db_private) != NULL) {
276         LOG(log_debug, logtype_cnid, "closing database connection for volume '%s'",
277             cdb->cnid_db_vol->v_localname);
278
279         free(db->cnid_mysql_voluuid_str);
280
281         close_prepared_stmt(db);
282
283         if (db->cnid_mysql_con)
284             mysql_close(db->cnid_mysql_con);
285         free(db);
286     }
287
288     free(cdb);
289
290     return;
291 }
292
293 int cnid_mysql_update(struct _cnid_db *cdb,
294                       cnid_t id,
295                       const struct stat *st,
296                       cnid_t did,
297                       const char *name,
298                       size_t len)
299 {
300     EC_INIT;
301     CNID_mysql_private *db;
302     cnid_t update_id;
303
304     if (!cdb || !(db = cdb->cnid_db_private) || !id || !st || !name) {
305         LOG(log_error, logtype_cnid, "cnid_update: Parameter error");
306         errno = CNID_ERR_PARAM;
307         EC_FAIL;
308     }
309
310     if (len > MAXPATHLEN) {
311         LOG(log_error, logtype_cnid, "cnid_update: Path name is too long");
312         errno = CNID_ERR_PATH;
313         EC_FAIL;
314     }
315
316     uint64_t dev = st->st_dev;
317     uint64_t ino = st->st_ino;
318
319     do {
320         EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
321                                     "DELETE FROM %s WHERE Id=%" PRIu32,
322                                     db->cnid_mysql_voluuid_str,
323                                     ntohl(id)) );
324         EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
325                                     "DELETE FROM %s WHERE Did=%" PRIu32 " AND Name='%s'",
326                                     db->cnid_mysql_voluuid_str, ntohl(did), name) );
327         EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
328                                     "DELETE FROM %s WHERE DevNo=%" PRIu64 " AND InodeNo=%" PRIu64,
329                                     db->cnid_mysql_voluuid_str, dev, ino) );
330
331         stmt_param_id = ntohl(id);
332         strncpy(stmt_param_name, name, sizeof(stmt_param_name));
333         stmt_param_name_len = len;
334         stmt_param_did = ntohl(did);
335         stmt_param_dev = dev;
336         stmt_param_ino = ino;
337
338         if (mysql_stmt_execute(db->cnid_put_stmt)) {
339             switch (mysql_stmt_errno(db->cnid_put_stmt)) {
340             case ER_DUP_ENTRY:
341                 /*
342                  * Race condition:
343                  * between deletion and insert another process may have inserted
344                  * this entry.
345                  */
346                 continue;
347             case CR_SERVER_LOST:
348                 close_prepared_stmt(db);
349                 EC_ZERO( init_prepared_stmt(db) );
350                 continue;
351             default:
352                 EC_FAIL;
353             }
354         }
355         update_id = mysql_stmt_insert_id(db->cnid_put_stmt);
356     } while (update_id != ntohl(id));
357
358 EC_CLEANUP:
359     EC_EXIT;
360 }
361
362 cnid_t cnid_mysql_lookup(struct _cnid_db *cdb,
363                          const struct stat *st,
364                          cnid_t did,
365                          const char *name,
366                          size_t len)
367 {
368     EC_INIT;
369     CNID_mysql_private *db;
370     cnid_t id = CNID_INVALID;
371     bool have_result = false;
372
373     if (!cdb || !(db = cdb->cnid_db_private) || !st || !name) {
374         LOG(log_error, logtype_cnid, "cnid_mysql_lookup: Parameter error");
375         errno = CNID_ERR_PARAM;
376         EC_FAIL;
377     }
378
379     if (len > MAXPATHLEN) {
380         LOG(log_error, logtype_cnid, "cnid_mysql_lookup: Path name is too long");
381         errno = CNID_ERR_PATH;
382         EC_FAIL;
383     }
384
385     uint64_t dev = st->st_dev;
386     uint64_t ino = st->st_ino;
387     cnid_t hint = db->cnid_mysql_hint;
388
389     LOG(log_maxdebug, logtype_cnid, "cnid_mysql_lookup(did: %" PRIu32 ", name: \"%s\", hint: %" PRIu32 "): START",
390         ntohl(did), name, ntohl(hint));
391
392     strncpy(stmt_param_name, name, sizeof(stmt_param_name));
393     stmt_param_name_len = len;
394     stmt_param_did = ntohl(did);
395     stmt_param_dev = dev;
396     stmt_param_ino = ino;
397
398 exec_stmt:
399     if (mysql_stmt_execute(db->cnid_lookup_stmt)) {
400         switch (mysql_stmt_errno(db->cnid_lookup_stmt)) {
401         case CR_SERVER_LOST:
402             close_prepared_stmt(db);
403             EC_ZERO( init_prepared_stmt(db) );
404             goto exec_stmt;
405         default:
406             EC_FAIL;
407         }
408     }
409     EC_ZERO_LOG( mysql_stmt_store_result(db->cnid_lookup_stmt) );
410     have_result = true;
411     EC_ZERO_LOG( mysql_stmt_bind_result(db->cnid_lookup_stmt, lookup_result) );
412
413     uint64_t retdev, retino;
414     cnid_t retid, retdid;
415     char *retname;
416
417     switch (mysql_stmt_num_rows(db->cnid_lookup_stmt)) {
418
419     case 0:
420         /* not found */
421         LOG(log_debug, logtype_cnid, "cnid_mysql_lookup: name: '%s', did: %u is not in the CNID database", 
422             name, ntohl(did));
423         errno = CNID_DBD_RES_NOTFOUND;
424         EC_FAIL;
425
426     case 1:
427         /* either both OR clauses matched the same id or only one matched, handled below */
428         EC_ZERO( mysql_stmt_fetch(db->cnid_lookup_stmt) );
429         break;
430
431     case 2:
432         /* a mismatch, delete both and return not found */
433         while (mysql_stmt_fetch(db->cnid_lookup_stmt) == 0) {
434             if (cnid_mysql_delete(cdb, htonl((cnid_t)lookup_result_id))) {
435                 LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
436                 errno = CNID_ERR_DB;
437                 EC_FAIL;
438             }
439         }
440         errno = CNID_DBD_RES_NOTFOUND;
441         EC_FAIL;
442
443     default:
444         errno = CNID_ERR_DB;
445         EC_FAIL;
446     }
447
448     retid = htonl(lookup_result_id);
449     retdid = htonl(lookup_result_did);
450     retname = lookup_result_name;
451     retdev = lookup_result_dev;
452     retino = lookup_result_ino;
453
454     if (retdid != did || STRCMP(retname, !=, name)) {
455         LOG(log_debug, logtype_cnid,
456             "cnid_mysql_lookup(CNID hint: %" PRIu32 ", DID: %" PRIu32 ", name: \"%s\"): server side mv oder reused inode",
457             ntohl(hint), ntohl(did), name);
458         if (hint != retid) {
459             if (cnid_mysql_delete(cdb, retid) != 0) {
460                 LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
461                 errno = CNID_ERR_DB;
462                 EC_FAIL;
463             }
464             errno = CNID_DBD_RES_NOTFOUND;
465             EC_FAIL;
466         }
467         LOG(log_debug, logtype_cnid, "cnid_mysql_lookup: server side mv, got hint, updating");
468         if (cnid_mysql_update(cdb, retid, st, did, name, len) != 0) {
469             LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
470             errno = CNID_ERR_DB;
471             EC_FAIL;
472         }
473         id = retid;
474     } else if (retdev != dev || retino != ino) {
475         LOG(log_debug, logtype_cnid, "cnid_mysql_lookup(DID:%u, name: \"%s\"): changed dev/ino",
476             ntohl(did), name);
477         if (cnid_mysql_delete(cdb, retid) != 0) {
478             LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
479             errno = CNID_ERR_DB;
480             EC_FAIL;
481         }
482         errno = CNID_DBD_RES_NOTFOUND;
483         EC_FAIL;
484     } else {
485         /* everythings good */
486         id = retid;
487     }
488
489 EC_CLEANUP:
490     LOG(log_debug, logtype_cnid, "cnid_mysql_lookup: id: %" PRIu32, ntohl(id));
491     if (have_result)
492         mysql_stmt_free_result(db->cnid_lookup_stmt);
493     if (ret != 0)
494         id = CNID_INVALID;
495     return id;
496 }
497
498 cnid_t cnid_mysql_add(struct _cnid_db *cdb,
499                       const struct stat *st,
500                       cnid_t did,
501                       const char *name,
502                       size_t len,
503                       cnid_t hint)
504 {
505     EC_INIT;
506     CNID_mysql_private *db;
507     cnid_t id = CNID_INVALID;
508     MYSQL_RES *result = NULL;
509     MYSQL_STMT *stmt;
510     my_ulonglong lastid;
511
512     if (!cdb || !(db = cdb->cnid_db_private) || !st || !name) {
513         LOG(log_error, logtype_cnid, "cnid_mysql_add: Parameter error");
514         errno = CNID_ERR_PARAM;
515         EC_FAIL;
516     }
517
518     if (len > MAXPATHLEN) {
519         LOG(log_error, logtype_cnid, "cnid_mysql_add: Path name is too long");
520         errno = CNID_ERR_PATH;
521         EC_FAIL;
522     }
523
524     uint64_t dev = st->st_dev;
525     uint64_t ino = st->st_ino;
526     db->cnid_mysql_hint = hint;
527
528     LOG(log_maxdebug, logtype_cnid, "cnid_mysql_add(did: %" PRIu32 ", name: \"%s\", hint: %" PRIu32 "): START",
529         ntohl(did), name, ntohl(hint));
530
531     do {
532         if ((id = cnid_mysql_lookup(cdb, st, did, name, len)) == CNID_INVALID) {
533             if (errno == CNID_ERR_DB)
534                 EC_FAIL;
535             /*
536              * If the CNID set overflowed before (CNID_MYSQL_FLAG_DEPLETED)
537              * ignore the CNID "hint" taken from the AppleDouble file
538              */
539             if (!db->cnid_mysql_hint || (db->cnid_mysql_flags & CNID_MYSQL_FLAG_DEPLETED)) {
540                 stmt = db->cnid_add_stmt;
541             } else {
542                 stmt = db->cnid_put_stmt;
543                 stmt_param_id = ntohl(db->cnid_mysql_hint);
544             }
545             strncpy(stmt_param_name, name, sizeof(stmt_param_name));
546             stmt_param_name_len = len;
547             stmt_param_did = ntohl(did);
548             stmt_param_dev = dev;
549             stmt_param_ino = ino;
550
551             if (mysql_stmt_execute(stmt)) {
552                 switch (mysql_stmt_errno(stmt)) {
553                 case ER_DUP_ENTRY:
554                     break;
555                 case CR_SERVER_LOST:
556                     close_prepared_stmt(db);
557                     EC_ZERO( init_prepared_stmt(db) );
558                     continue;
559                 default:
560                     EC_FAIL;
561                 }
562                 /*
563                  * Race condition:
564                  * between lookup and insert another process may have inserted
565                  * this entry.
566                  */
567                 if (db->cnid_mysql_hint)
568                     db->cnid_mysql_hint = CNID_INVALID;
569                 continue;
570             }
571
572             lastid = mysql_stmt_insert_id(stmt);
573
574             if (lastid > 0xffffffff) {
575                 /* CNID set ist depleted, restart from scratch */
576                 EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
577                                             "START TRANSACTION;"
578                                             "UPDATE volumes SET Depleted=1 WHERE VolUUID='%s';"
579                                             "TRUNCATE TABLE %s;"
580                                             "ALTER TABLE %s AUTO_INCREMENT = 17;" 
581                                             "COMMIT;",
582                                             db->cnid_mysql_voluuid_str,
583                                             db->cnid_mysql_voluuid_str,
584                                             db->cnid_mysql_voluuid_str) );
585                 db->cnid_mysql_flags |= CNID_MYSQL_FLAG_DEPLETED;
586                 hint = CNID_INVALID;
587                 do {
588                     result = mysql_store_result(db->cnid_mysql_con);
589                     if (result)
590                         mysql_free_result(result);
591                 } while (mysql_next_result(db->cnid_mysql_con) == 0);
592                 continue;
593             }
594
595             /* Finally assign our result */
596             id = htonl((uint32_t)lastid);
597         }
598     } while (id == CNID_INVALID);
599
600 EC_CLEANUP:
601     LOG(log_debug, logtype_cnid, "cnid_mysql_add: id: %" PRIu32, ntohl(id));
602
603     if (result)
604         mysql_free_result(result);
605     return id;
606 }
607
608 cnid_t cnid_mysql_get(struct _cnid_db *cdb, cnid_t did, const char *name, size_t len)
609 {
610     EC_INIT;
611     CNID_mysql_private *db;
612     cnid_t id = CNID_INVALID;
613     MYSQL_RES *result = NULL;
614     MYSQL_ROW row;
615
616     if (!cdb || !(db = cdb->cnid_db_private) || !name) {
617         LOG(log_error, logtype_cnid, "cnid_mysql_get: Parameter error");
618         errno = CNID_ERR_PARAM;
619         EC_FAIL;
620     }
621
622     if (len > MAXPATHLEN) {
623         LOG(log_error, logtype_cnid, "cnid_mysql_get: name is too long");
624         errno = CNID_ERR_PATH;
625         return CNID_INVALID;
626     }
627
628     LOG(log_debug, logtype_cnid, "cnid_mysql_get(did: %" PRIu32 ", name: \"%s\"): START",
629         ntohl(did),name);
630
631     EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con,
632                                 "SELECT Id FROM %s "
633                                 "WHERE Name='%s' AND Did=%" PRIu32,
634                                 db->cnid_mysql_voluuid_str,
635                                 name,
636                                 ntohl(did)) );
637
638     if ((result = mysql_store_result(db->cnid_mysql_con)) == NULL) {
639         LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
640         errno = CNID_ERR_DB;
641         EC_FAIL;
642     }
643
644     if (mysql_num_rows(result)) {
645         row = mysql_fetch_row(result);
646         id = htonl(atoi(row[0]));
647     }
648
649 EC_CLEANUP:
650     LOG(log_debug, logtype_cnid, "cnid_mysql_get: id: %" PRIu32, ntohl(id));
651
652     if (result)
653         mysql_free_result(result);
654
655     return id;
656 }
657
658 char *cnid_mysql_resolve(struct _cnid_db *cdb, cnid_t *id, void *buffer, size_t len)
659 {
660     EC_INIT;
661     CNID_mysql_private *db;
662     MYSQL_RES *result = NULL;
663     MYSQL_ROW row;
664
665     if (!cdb || !(db = cdb->cnid_db_private)) {
666         LOG(log_error, logtype_cnid, "cnid_mysql_get: Parameter error");
667         errno = CNID_ERR_PARAM;
668         EC_FAIL;
669     }
670
671     EC_NEG1( cnid_mysql_execute(
672                  db->cnid_mysql_con,
673                  "SELECT Did,Name FROM %s WHERE Id=%" PRIu32,
674                  db->cnid_mysql_voluuid_str, ntohl(*id)) );
675
676     EC_NULL( result = mysql_store_result(db->cnid_mysql_con) );
677
678     if (mysql_num_rows(result) != 1)
679         EC_FAIL;
680
681     row = mysql_fetch_row(result);
682
683     *id = htonl(atoi(row[0]));
684     strncpy(buffer, row[1], len);
685
686 EC_CLEANUP:             
687     if (result)
688         mysql_free_result(result);
689
690     if (ret != 0) {
691         *id = CNID_INVALID;
692         return NULL;
693     }
694     return buffer;
695 }
696
697 /**
698  * Caller passes buffer where we will store the db stamp
699  **/
700 int cnid_mysql_getstamp(struct _cnid_db *cdb, void *buffer, const size_t len)
701 {
702     EC_INIT;
703     CNID_mysql_private *db;
704     MYSQL_RES *result = NULL;
705     MYSQL_ROW row;
706
707     if (!cdb || !(db = cdb->cnid_db_private)) {
708         LOG(log_error, logtype_cnid, "cnid_find: Parameter error");
709         errno = CNID_ERR_PARAM;
710         return CNID_INVALID;
711     }
712
713     if (!buffer)
714         EC_EXIT_STATUS(0);
715
716     if (cnid_mysql_execute(db->cnid_mysql_con,
717                            "SELECT Stamp FROM volumes WHERE VolPath='%s'",
718                            cdb->cnid_db_vol->v_path)) {
719         if (mysql_errno(db->cnid_mysql_con) != ER_DUP_ENTRY) {
720             LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
721             EC_FAIL;
722         }
723     }
724
725     if ((result = mysql_store_result(db->cnid_mysql_con)) == NULL) {
726         LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
727         errno = CNID_ERR_DB;
728         EC_FAIL;
729     }
730     if (!mysql_num_rows(result)) {
731         LOG(log_error, logtype_cnid, "Can't get DB stamp for volumes \"%s\"", cdb->cnid_db_vol->v_path);
732         EC_FAIL;
733     }
734     row = mysql_fetch_row(result);
735     memcpy(buffer, row[0], len);
736
737 EC_CLEANUP:
738     if (result)
739         mysql_free_result(result);
740     EC_EXIT;
741 }
742
743
744 int cnid_mysql_find(struct _cnid_db *cdb, const char *name, size_t namelen, void *buffer, size_t buflen)
745 {
746     LOG(log_error, logtype_cnid,
747         "cnid_mysql_find(\"%s\"): not supported with MySQL CNID backend", name);
748     return -1;
749 }
750
751 cnid_t cnid_mysql_rebuild_add(struct _cnid_db *cdb, const struct stat *st,
752                               cnid_t did, const char *name, size_t len, cnid_t hint)
753 {
754     LOG(log_error, logtype_cnid,
755         "cnid_mysql_rebuild_add(\"%s\"): not supported with MySQL CNID backend", name);
756     return CNID_INVALID;
757 }
758
759 int cnid_mysql_wipe(struct _cnid_db *cdb)
760 {
761     EC_INIT;
762     CNID_mysql_private *db;
763     MYSQL_RES *result = NULL;
764
765     if (!cdb || !(db = cdb->cnid_db_private)) {
766         LOG(log_error, logtype_cnid, "cnid_wipe: Parameter error");
767         errno = CNID_ERR_PARAM;
768         return -1;
769     }
770
771     LOG(log_debug, logtype_cnid, "cnid_dbd_wipe");
772
773     EC_NEG1( cnid_mysql_execute(
774                  db->cnid_mysql_con,
775                  "START TRANSACTION;"
776                  "UPDATE volumes SET Depleted=0 WHERE VolUUID='%s';"
777                  "TRUNCATE TABLE %s;"
778                  "ALTER TABLE %s AUTO_INCREMENT = 17;"
779                  "COMMIT;",
780                  db->cnid_mysql_voluuid_str,
781                  db->cnid_mysql_voluuid_str,
782                  db->cnid_mysql_voluuid_str) );
783
784     do {
785         result = mysql_store_result(db->cnid_mysql_con);
786         if (result)
787             mysql_free_result(result);
788     } while (mysql_next_result(db->cnid_mysql_con) == 0);
789
790 EC_CLEANUP:
791     EC_EXIT;
792 }
793
794 static struct _cnid_db *cnid_mysql_new(struct vol *vol)
795 {
796     struct _cnid_db *cdb;
797
798     if ((cdb = (struct _cnid_db *)calloc(1, sizeof(struct _cnid_db))) == NULL)
799         return NULL;
800
801     cdb->cnid_db_vol = vol;
802     cdb->cnid_db_flags = CNID_FLAG_PERSISTENT | CNID_FLAG_LAZY_INIT;
803     cdb->cnid_add = cnid_mysql_add;
804     cdb->cnid_delete = cnid_mysql_delete;
805     cdb->cnid_get = cnid_mysql_get;
806     cdb->cnid_lookup = cnid_mysql_lookup;
807     cdb->cnid_find = cnid_mysql_find;
808     cdb->cnid_nextid = NULL;
809     cdb->cnid_resolve = cnid_mysql_resolve;
810     cdb->cnid_getstamp = cnid_mysql_getstamp;
811     cdb->cnid_update = cnid_mysql_update;
812     cdb->cnid_rebuild_add = cnid_mysql_rebuild_add;
813     cdb->cnid_close = cnid_mysql_close;
814     cdb->cnid_wipe = cnid_mysql_wipe;
815     return cdb;
816 }
817
818 static const char *printuuid(const unsigned char *uuid) {
819     static char uuidstring[64];
820     int i = 0;
821     unsigned char c;
822
823     while ((c = *uuid)) {
824         uuid++;
825         sprintf(uuidstring + i, "%02X", c);
826         i += 2;
827     }
828     uuidstring[i] = 0;
829     return uuidstring;
830 }
831
832 /* ---------------------- */
833 struct _cnid_db *cnid_mysql_open(struct cnid_open_args *args)
834 {
835     EC_INIT;
836     CNID_mysql_private *db = NULL;
837     struct _cnid_db *cdb = NULL;
838     MYSQL_RES *result = NULL;
839     MYSQL_ROW row;
840     struct vol *vol = args->cnid_args_vol;
841
842     EC_NULL( cdb = cnid_mysql_new(vol) );
843     EC_NULL( db = (CNID_mysql_private *)calloc(1, sizeof(CNID_mysql_private)) );
844     cdb->cnid_db_private = db;
845
846     db->cnid_mysql_voluuid_str = strdup(printuuid(vol->v_uuid));
847
848     /* Initialize and connect to MySQL server */
849     EC_NULL( db->cnid_mysql_con = mysql_init(NULL) );
850     my_bool my_recon = true;
851     EC_ZERO( mysql_options(db->cnid_mysql_con, MYSQL_OPT_RECONNECT, &my_recon) );
852     int my_timeout = 600;
853     EC_ZERO( mysql_options(db->cnid_mysql_con, MYSQL_OPT_CONNECT_TIMEOUT, &my_timeout) );
854
855     const AFPObj *obj = vol->v_obj;
856
857     EC_NULL( mysql_real_connect(db->cnid_mysql_con,
858                                 obj->options.cnid_mysql_host,
859                                 obj->options.cnid_mysql_user,
860                                 obj->options.cnid_mysql_pw,
861                                 obj->options.cnid_mysql_db,
862                                 0, NULL, CLIENT_MULTI_STATEMENTS));
863
864     /* Add volume to volume table */
865     if (cnid_mysql_execute(db->cnid_mysql_con,
866                            "CREATE TABLE IF NOT EXISTS volumes"
867                            "(VolUUID CHAR(32) PRIMARY KEY,VolPath TEXT(4096),Stamp BINARY(8),Depleted INT, INDEX(VolPath(64)))")) {
868         LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
869         EC_FAIL;
870     }
871     time_t now = time(NULL);
872     char stamp[8];
873     memset(stamp, 0, 8);
874     memcpy(stamp, &now, sizeof(time_t));
875     char blob[16+1];
876     mysql_real_escape_string(db->cnid_mysql_con, blob, stamp, 8);
877
878     if (cnid_mysql_execute(db->cnid_mysql_con,
879                            "INSERT INTO volumes (VolUUID,Volpath,Stamp,Depleted) "
880                            "VALUES('%s','%s','%s',0)",
881                            db->cnid_mysql_voluuid_str,
882                            vol->v_path,
883                            blob)) {
884         if (mysql_errno(db->cnid_mysql_con) != ER_DUP_ENTRY) {
885             LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
886             EC_FAIL;
887         }
888     }
889
890     /*
891      * Check whether CNID set overflowed before.
892      * If that's the case, in cnid_mysql_add() we'll ignore the CNID "hint" taken from the
893      * AppleDouble file.
894      */
895     if (cnid_mysql_execute(db->cnid_mysql_con,
896                            "SELECT Depleted FROM volumes WHERE VolUUID='%s'",
897                            db->cnid_mysql_voluuid_str)) {
898         LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
899         EC_FAIL;
900     }
901     if ((result = mysql_store_result(db->cnid_mysql_con)) == NULL) {
902         LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
903         errno = CNID_ERR_DB;
904         EC_FAIL;
905     }
906     if (mysql_num_rows(result)) {
907         row = mysql_fetch_row(result);
908         int depleted = atoi(row[0]);
909         if (depleted)
910             db->cnid_mysql_flags |= CNID_MYSQL_FLAG_DEPLETED;
911     }
912     mysql_free_result(result);
913     result = NULL;
914
915     /* Create volume table */
916     if (cnid_mysql_execute(db->cnid_mysql_con,
917                            "CREATE TABLE IF NOT EXISTS %s"
918                            "(Id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,"
919                            "Name VARCHAR(255) NOT NULL,"
920                            "Did INT UNSIGNED NOT NULL,"
921                            "DevNo BIGINT UNSIGNED NOT NULL,"
922                            "InodeNo BIGINT UNSIGNED NOT NULL,"
923                            "UNIQUE DidName(Did, Name), UNIQUE DevIno(DevNo, InodeNo)) "
924                            "AUTO_INCREMENT=17",
925                            db->cnid_mysql_voluuid_str)) {
926         LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con));
927         EC_FAIL;
928     }
929
930     EC_ZERO( init_prepared_stmt(db) );
931
932     LOG(log_debug, logtype_cnid, "Finished initializing MySQL CNID module for volume '%s'",
933         vol->v_path);
934
935 EC_CLEANUP:
936     if (result)
937         mysql_free_result(result);
938     if (ret != 0) {
939         if (cdb)
940             free(cdb);
941         cdb = NULL;
942         if (db)
943             free(db);
944     }
945     return cdb;
946 }
947
948 struct _cnid_module cnid_mysql_module = {
949     "mysql",
950     {NULL, NULL},
951     cnid_mysql_open,
952     0
953 };