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