server: Remove unused ->peer from struct command_context.
[paraslash.git] / blob.c
1 /*
2  * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file blob.c Macros and functions for blob handling. */
8
9 #include <regex.h>
10 #include <fnmatch.h>
11 #include <osl.h>
12 #include <lopsub.h>
13
14 #include "server_cmd.lsg.h"
15 #include "para.h"
16 #include "error.h"
17 #include "crypt.h"
18 #include "string.h"
19 #include "afh.h"
20 #include "afs.h"
21 #include "ipc.h"
22 #include "portable_io.h"
23 #include "sideband.h"
24 #include "command.h"
25
26 /**
27  * Compare two osl objects pointing to unsigned integers of 32 bit size.
28  *
29  * \param obj1 Pointer to the first integer.
30  * \param obj2 Pointer to the second integer.
31  *
32  * \return The values required for an osl compare function.
33  */
34 static int uint32_compare(const struct osl_object *obj1, const struct osl_object *obj2)
35 {
36         uint32_t d1 = read_u32((const char *)obj1->data);
37         uint32_t d2 = read_u32((const char *)obj2->data);
38
39         if (d1 < d2)
40                 return 1;
41         if (d1 > d2)
42                 return -1;
43         return 0;
44 }
45
46 static struct osl_column_description blob_cols[] = {
47         [BLOBCOL_ID] = {
48                 .storage_type = OSL_MAPPED_STORAGE,
49                 .storage_flags = OSL_RBTREE | OSL_UNIQUE | OSL_FIXED_SIZE,
50                 .name = "id",
51                 .data_size = 4,
52                 .compare_function = uint32_compare
53         },
54         [BLOBCOL_NAME] = {
55                 .storage_type = OSL_MAPPED_STORAGE,
56                 .storage_flags = OSL_RBTREE | OSL_UNIQUE,
57                 .name = "name",
58                 .compare_function = string_compare
59         },
60         [BLOBCOL_DEF] = {
61                 .storage_type = OSL_DISK_STORAGE,
62                 .storage_flags = 0,
63                 .name = "definition"
64         }
65 };
66
67 /** Define an osl table description for a blob table. */
68 #define DEFINE_BLOB_TABLE_DESC(table_name) \
69         static struct osl_table_description table_name ## _table_desc = { \
70                 .name = #table_name, \
71                 .num_columns = NUM_BLOB_COLUMNS, \
72                 .flags = OSL_LARGE_TABLE, \
73                 .column_descriptions = blob_cols \
74         };
75
76 /** Define a pointer to an osl blob table with a canonical name. */
77 #define DEFINE_BLOB_TABLE_PTR(table_name) struct osl_table *table_name ## _table;
78
79
80 /** Define a blob table. */
81 #define INIT_BLOB_TABLE(table_name) \
82         DEFINE_BLOB_TABLE_DESC(table_name); \
83         DEFINE_BLOB_TABLE_PTR(table_name);
84
85 /* doxygen isn't smart enough to recognize these */
86 /** \cond blob_table */
87 INIT_BLOB_TABLE(lyrics);
88 INIT_BLOB_TABLE(images);
89 INIT_BLOB_TABLE(moods);
90 INIT_BLOB_TABLE(playlists);
91 /** \endcond blob_table */
92
93 static int print_blob(struct osl_table *table, struct osl_row *row,
94                 const char *name, void *data)
95 {
96         struct afs_callback_arg *aca = data;
97         bool l_given = SERVER_CMD_OPT_GIVEN(LSMOOD, LONG, aca->lpr);
98         struct osl_object obj;
99         uint32_t id;
100         int ret;
101
102         if (!l_given) {
103                 para_printf(&aca->pbout, "%s\n", name);
104                 return 0;
105         }
106         ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
107         if (ret < 0) {
108                 para_printf(&aca->pbout, "cannot list %s\n", name);
109                 return ret;
110         }
111         id = *(uint32_t *)obj.data;
112         para_printf(&aca->pbout, "%u\t%s\n", id, name);
113         return 1;
114 }
115
116 static int com_lsblob_callback(const struct lls_command * const cmd,
117                 struct osl_table *table, struct afs_callback_arg *aca)
118 {
119         bool i_given, r_given;
120         struct pattern_match_data pmd = {
121                 .table = table,
122                 .pm_flags = PM_NO_PATTERN_MATCHES_EVERYTHING | PM_SKIP_EMPTY_NAME,
123                 .match_col_num = BLOBCOL_NAME,
124                 .data = aca,
125                 .action = print_blob,
126         };
127         int ret;
128         ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
129         pmd.lpr = aca->lpr;
130         assert(ret >= 0);
131         i_given = SERVER_CMD_OPT_GIVEN(LSMOOD, ID_SORT, aca->lpr);
132         r_given = SERVER_CMD_OPT_GIVEN(LSMOOD, REVERSE, aca->lpr);
133
134         if (r_given)
135                 pmd.pm_flags |= PM_REVERSE_LOOP;
136         if (i_given)
137                 pmd.loop_col_num = BLOBCOL_ID;
138         else
139                 pmd.loop_col_num = BLOBCOL_NAME;
140         ret = for_each_matching_row(&pmd);
141         if (ret < 0)
142                 goto out;
143         if (pmd.num_matches == 0 && lls_num_inputs(aca->lpr) > 0)
144                 ret = -E_NO_MATCH;
145 out:
146         lls_free_parse_result(aca->lpr, cmd);
147         return ret;
148 }
149
150 static int com_lsblob(afs_callback *f, const struct lls_command * const cmd,
151                 struct command_context *cc, struct lls_parse_result *lpr)
152 {
153         return send_lls_callback_request(f, cmd, lpr, cc);
154 }
155
156 static int cat_blob(struct osl_table *table, struct osl_row *row,
157                 __a_unused const char *name, void *data)
158 {
159         int ret = 0, ret2, fd = *(int *)data;
160         struct osl_object obj;
161
162         ret = osl(osl_open_disk_object(table, row, BLOBCOL_DEF, &obj));
163         if (ret < 0)
164                 return (ret == osl(-E_OSL_EMPTY))? 0 : ret;
165         assert(obj.size > 0);
166         ret = pass_buffer_as_shm(fd, SBD_OUTPUT, obj.data, obj.size);
167         ret2 = osl(osl_close_disk_object(&obj));
168         return (ret < 0)? ret : ret2;
169 }
170
171 static int com_catblob_callback(const struct lls_command * const cmd,
172                 struct osl_table *table, struct afs_callback_arg *aca)
173 {
174         int ret;
175         struct pattern_match_data pmd = {
176                 .table = table,
177                 .loop_col_num = BLOBCOL_NAME,
178                 .match_col_num = BLOBCOL_NAME,
179                 .pm_flags = PM_SKIP_EMPTY_NAME,
180                 .data = &aca->fd,
181                 .action = cat_blob
182         };
183         ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
184         assert(ret >= 0);
185         pmd.lpr = aca->lpr;
186         ret = for_each_matching_row(&pmd);
187         if (ret < 0)
188                 goto out;
189         if (pmd.num_matches == 0)
190                 ret = -E_NO_MATCH;
191 out:
192         lls_free_parse_result(aca->lpr, cmd);
193         return ret;
194 }
195
196 static int com_catblob(afs_callback *f, const struct lls_command * const cmd,
197                 struct command_context *cc, struct lls_parse_result *lpr)
198 {
199         char *errctx;
200         int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
201
202         if (ret < 0) {
203                 send_errctx(cc, errctx);
204                 return ret;
205         }
206         return send_lls_callback_request(f, cmd, lpr, cc);
207 }
208
209 static int remove_blob(struct osl_table *table, struct osl_row *row,
210                 const char *name, void *data)
211 {
212         struct afs_callback_arg *aca = data;
213         int ret = osl(osl_del_row(table, row));
214         if (ret < 0) {
215                 para_printf(&aca->pbout, "cannot remove %s\n", name);
216                 return ret;
217         }
218         return 1;
219 }
220
221 static int com_rmblob_callback(const struct lls_command * const cmd,
222                 struct osl_table *table, struct afs_callback_arg *aca)
223 {
224         int ret;
225         struct pattern_match_data pmd = {
226                 .table = table,
227                 .loop_col_num = BLOBCOL_NAME,
228                 .match_col_num = BLOBCOL_NAME,
229                 .pm_flags = PM_SKIP_EMPTY_NAME,
230                 .data = aca,
231                 .action = remove_blob
232         };
233         ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
234         assert(ret >= 0);
235         pmd.lpr = aca->lpr;
236         ret = for_each_matching_row(&pmd);
237         if (ret < 0)
238                 goto out;
239         if (pmd.num_matches == 0)
240                 ret = -E_NO_MATCH;
241         else {
242                 para_printf(&aca->pbout, "removed %u blob(s)\n",
243                         pmd.num_matches);
244                 ret = afs_event(BLOB_REMOVE, NULL, table);
245         }
246 out:
247         lls_free_parse_result(aca->lpr, cmd);
248         return ret;
249 }
250
251 static int com_rmblob(afs_callback *f, const struct lls_command * const cmd,
252                 struct command_context *cc, struct lls_parse_result *lpr)
253 {
254         char *errctx;
255         int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
256
257         if (ret < 0) {
258                 send_errctx(cc, errctx);
259                 return ret;
260         }
261         return send_lls_callback_request(f, cmd, lpr, cc);
262 }
263
264 static int com_addblob_callback(__a_unused const struct lls_command * const cmd,
265                 struct osl_table *table, struct afs_callback_arg *aca)
266 {
267         struct osl_object objs[NUM_BLOB_COLUMNS];
268         char *name = aca->query.data;
269         size_t name_len = strlen(name) + 1;
270         uint32_t id;
271         unsigned num_rows;
272         int ret;
273
274         ret = osl(osl_get_num_rows(table, &num_rows));
275         if (ret < 0)
276                 goto out;
277         if (!num_rows) { /* this is the first entry ever added */
278                 /* insert dummy row containing the id */
279                 id = 2; /* this entry will be entry #1, so 2 is the next */
280                 objs[BLOBCOL_ID].data = &id;
281                 objs[BLOBCOL_ID].size = sizeof(id);
282                 objs[BLOBCOL_NAME].data = "";
283                 objs[BLOBCOL_NAME].size = 1;
284                 objs[BLOBCOL_DEF].data = "";
285                 objs[BLOBCOL_DEF].size = 1;
286                 ret = osl(osl_add_row(table, objs));
287                 if (ret < 0)
288                         goto out;
289         } else {
290                 /* check if name already exists */
291                 struct osl_row *row;
292                 struct osl_object obj = {.data = name, .size = name_len};
293                 ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
294                 if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
295                         goto out;
296                 if (ret >= 0) { /* we already have a blob with this name */
297                         ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
298                         if (ret < 0)
299                                 goto out;
300                         id = *(uint32_t *)obj.data;
301                         obj.data = name + name_len;
302                         obj.size = aca->query.size - name_len;
303                         ret = osl(osl_update_object(table, row, BLOBCOL_DEF, &obj));
304                         goto out;
305                 }
306                 /* new blob, get id of the dummy row and increment it */
307                 obj.data = "";
308                 obj.size = 1;
309                 ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
310                 if (ret < 0)
311                         goto out;
312                 ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
313                 if (ret < 0)
314                         goto out;
315                 id = *(uint32_t *)obj.data + 1;
316                 obj.data = &id;
317                 ret = osl(osl_update_object(table, row, BLOBCOL_ID, &obj));
318                 if (ret < 0)
319                         goto out;
320         }
321         id--;
322         objs[BLOBCOL_ID].data = &id;
323         objs[BLOBCOL_ID].size = sizeof(id);
324         objs[BLOBCOL_NAME].data = name;
325         objs[BLOBCOL_NAME].size = name_len;
326         objs[BLOBCOL_DEF].data = name + name_len;
327         objs[BLOBCOL_DEF].size = aca->query.size - name_len;
328         ret = osl(osl_add_row(table, objs));
329         if (ret < 0)
330                 goto out;
331         ret = afs_event(BLOB_ADD, NULL, table);
332 out:
333         if (ret < 0)
334                 para_printf(&aca->pbout, "cannot add %s\n", name);
335         else
336                 para_printf(&aca->pbout, "added %s as id %u\n", name, id);
337         return ret;
338 }
339
340 /* Write input from fd to dynamically allocated buffer, but maximal 10M. */
341 static int fd2buf(struct stream_cipher_context *scc, struct osl_object *obj)
342 {
343         size_t max_size = 10 * 1024 * 1024;
344         int ret;
345         struct iovec iov;
346
347         obj->data = NULL;
348         obj->size = 0;
349 again:
350         do {
351                 ret = recv_sb(scc, SBD_BLOB_DATA, max_size, &iov);
352         } while (ret == 0);
353
354         if (ret < 0) {
355                 free(obj->data);
356                 obj->data = NULL;
357                 obj->size = 0;
358                 return ret;
359         }
360         if (iov.iov_len == 0) /* end of blob */
361                 return 1;
362         if (!obj->data) {
363                 obj->data = iov.iov_base;
364                 obj->size = iov.iov_len;
365         } else {
366                 obj->data = para_realloc(obj->data, obj->size + iov.iov_len);
367                 memcpy(obj->data + obj->size, iov.iov_base, iov.iov_len);
368                 obj->size += iov.iov_len;
369                 free(iov.iov_base);
370                 max_size -= iov.iov_len;
371         }
372         goto again;
373         return 1;
374 }
375
376 /*
377  * Read blob from a file descriptor and send it to afs.
378  *
379  * This function is called from the addblob command handlers to instruct the
380  * afs process to store the input in a blob table. Input is read and decrypted
381  * from the file descriptor given by cc and appended to arg_obj, which contains
382  * the name of the blob to create. The combined buffer is made available to the
383  * afs process via the callback method.
384  */
385 static int stdin_command(struct command_context *cc,
386                 struct lls_parse_result *lpr, afs_callback *f)
387 {
388         struct osl_object query, stdin_obj;
389         int ret;
390         size_t len = strlen(lls_input(0, lpr));
391
392         ret = send_sb(&cc->scc, NULL, 0, SBD_AWAITING_DATA, false);
393         if (ret < 0)
394                 return ret;
395         ret = fd2buf(&cc->scc, &stdin_obj);
396         if (ret < 0)
397                 return ret;
398         query.size = len + 1 + stdin_obj.size;
399         query.data = para_malloc(query.size);
400         memcpy(query.data, lls_input(0, lpr), len + 1);
401         if (stdin_obj.size > 0)
402                 memcpy((char *)query.data + len + 1, stdin_obj.data,
403                         stdin_obj.size);
404         free(stdin_obj.data);
405         ret = send_callback_request(f, &query, afs_cb_result_handler, cc);
406         free(query.data);
407         return ret;
408 }
409
410 static int com_addblob(afs_callback *f, __a_unused const struct lls_command * const cmd,
411                 struct command_context *cc, struct lls_parse_result *lpr)
412 {
413         char *errctx;
414         int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
415
416         if (ret < 0) {
417                 send_errctx(cc, errctx);
418                 return ret;
419         }
420         if (!lls_input(0, lpr)[0]) /* empty name is reserved for the dummy row */
421                 return -E_BLOB_SYNTAX;
422         return stdin_command(cc, lpr, f);
423 }
424
425 static int com_mvblob_callback(const struct lls_command * const cmd,
426                 struct osl_table *table, struct afs_callback_arg *aca)
427 {
428         const char *src, *dest;
429         struct osl_object obj;
430         struct osl_row *row;
431         int ret;
432
433         ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
434         assert(ret >= 0);
435         src = lls_input(0, aca->lpr);
436         dest = lls_input(1, aca->lpr);
437         obj.data = (char *)src;
438         obj.size = strlen(src) + 1;
439         ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
440
441         if (ret < 0) {
442                 para_printf(&aca->pbout, "cannot find source blob %s\n", src);
443                 goto out;
444         }
445         obj.data = (char *)dest;
446         obj.size = strlen(dest) + 1;
447         ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj));
448         if (ret < 0) {
449                 para_printf(&aca->pbout, "cannot rename blob %s to %s\n",
450                         src, dest);
451                 goto out;
452         }
453         ret = afs_event(BLOB_RENAME, NULL, table);
454 out:
455         lls_free_parse_result(aca->lpr, cmd);
456         return ret;
457 }
458
459 static int com_mvblob(afs_callback *f, const struct lls_command * const cmd,
460                 struct command_context *cc, struct lls_parse_result *lpr)
461 {
462         char *errctx;
463         int ret = lls(lls_check_arg_count(lpr, 2, 2, &errctx));
464
465         if (ret < 0) {
466                 send_errctx(cc, errctx);
467                 return ret;
468         }
469         return send_lls_callback_request(f, cmd, lpr, cc);
470 }
471
472 #define DEFINE_BLOB_COMMAND(cmd_name, c_cmd_name, table_name, short_name, c_short_name) \
473         static int com_ ## cmd_name ## short_name ## _callback(struct afs_callback_arg *aca) \
474         { \
475                 const struct lls_command *cmd = SERVER_CMD_CMD_PTR(c_cmd_name ## c_short_name); \
476                 return com_ ## cmd_name ## blob_callback(cmd, table_name ## _table, aca); \
477         } \
478         static int com_ ## cmd_name ## short_name(struct command_context *cc, struct lls_parse_result *lpr) \
479         { \
480                 const struct lls_command *cmd = SERVER_CMD_CMD_PTR(c_cmd_name ## c_short_name); \
481                 return com_ ## cmd_name ## blob(com_ ## cmd_name ## short_name ## _callback, cmd, cc, lpr); \
482         } \
483         EXPORT_SERVER_CMD_HANDLER(cmd_name ## short_name);
484
485 static int blob_get_name_by_id(struct osl_table *table, uint32_t id,
486                 char **name)
487 {
488         struct osl_row *row;
489         struct osl_object obj = {.data = &id, .size = sizeof(id)};
490         int ret;
491
492         if (name)
493                 *name = NULL;
494         if (!id)
495                 return 1;
496         ret = osl(osl_get_row(table, BLOBCOL_ID, &obj, &row));
497         if (ret < 0)
498                 return ret;
499         ret = osl(osl_get_object(table, row, BLOBCOL_NAME, &obj));
500         if (ret < 0)
501                 return ret;
502         if (*(char *)obj.data == '\0')
503                 return -E_DUMMY_ROW;
504         if (name)
505                 *name = (char *)obj.data;
506         return 1;
507 }
508
509 /** Define the \p get_name_by_id function for this blob type. */
510 #define DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix) \
511         int cmd_prefix ## _get_name_by_id(uint32_t id, char **name) \
512         { \
513                 return blob_get_name_by_id(table_name ## _table, id, name); \
514         }
515
516
517 static int blob_get_def_by_name(struct osl_table *table, char *name,
518                 struct osl_object *def)
519 {
520         struct osl_row *row;
521         struct osl_object obj = {.data = name, .size = strlen(name) + 1};
522         int ret;
523
524         def->data = NULL;
525         if (!*name)
526                 return 1;
527         ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
528         if (ret < 0)
529                 return ret;
530         return osl(osl_open_disk_object(table, row, BLOBCOL_DEF, def));
531 }
532
533 /** Define the \p get_def_by_id function for this blob type. */
534 #define DEFINE_GET_DEF_BY_NAME(table_name, cmd_prefix) \
535         int cmd_prefix ## _get_def_by_name(char *name, struct osl_object *def) \
536         { \
537                 return blob_get_def_by_name(table_name ## _table, name, def); \
538         }
539
540 static int blob_get_def_by_id(struct osl_table *table, uint32_t id,
541                 struct osl_object *def)
542 {
543         struct osl_row *row;
544         struct osl_object obj = {.data = &id, .size = sizeof(id)};
545         int ret;
546
547         def->data = NULL;
548         if (!id)
549                 return 1;
550         ret = osl(osl_get_row(table, BLOBCOL_ID, &obj, &row));
551         if (ret < 0)
552                 return ret;
553         return osl(osl_open_disk_object(table, row, BLOBCOL_DEF, def));
554 }
555
556 /** Define the \p get_def_by_id function for this blob type. */
557 #define DEFINE_GET_DEF_BY_ID(table_name, cmd_prefix) \
558         int cmd_prefix ## _get_def_by_id(uint32_t id, struct osl_object *def) \
559         { \
560                 return blob_get_def_by_id(table_name ## _table, id, def); \
561         }
562
563 static int blob_get_name_and_def_by_row(struct osl_table *table,
564                 const struct osl_row *row, char **name, struct osl_object *def)
565 {
566         struct osl_object obj;
567         int ret = osl(osl_get_object(table, row, BLOBCOL_NAME, &obj));
568         if (ret < 0)
569                 return ret;
570         *name = obj.data;
571         return osl(osl_open_disk_object(table, row, BLOBCOL_DEF, def));
572 }
573 /** Define the \p get_name_and_def_by_row function for this blob type. */
574 #define DEFINE_GET_NAME_AND_DEF_BY_ROW(table_name, cmd_prefix) \
575         int cmd_prefix ## _get_name_and_def_by_row(const struct osl_row *row, \
576                 char **name, struct osl_object *def) \
577         { \
578                 return blob_get_name_and_def_by_row(table_name ## _table, \
579                         row, name, def); \
580         }
581
582 /** Define the \p close function for this blob type. */
583 #define DEFINE_BLOB_CLOSE(table_name) \
584         static void table_name ## _close(void) \
585         { \
586                 osl_close_table(table_name ## _table, OSL_MARK_CLEAN); \
587                 table_name ## _table = NULL; \
588         }
589
590 /** Define the \p create function for this blob type. */
591 #define DEFINE_BLOB_CREATE(table_name) \
592         static int table_name ## _create(const char *dir) \
593         { \
594                 table_name ## _table_desc.dir = dir; \
595                 return osl(osl_create_table(&table_name ## _table_desc)); \
596         }
597
598 static int blob_open(struct osl_table **table,
599                 struct osl_table_description *desc,
600                 const char *dir)
601 {
602         int ret;
603         desc->dir = dir;
604         ret = osl(osl_open_table(desc, table));
605         if (ret >= 0)
606                 return ret;
607         *table = NULL;
608         if (ret >= 0 || ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT))
609                 return 1;
610         return ret;
611 }
612
613 #define DEFINE_BLOB_OPEN(table_name) \
614         static int table_name ## _open(const char *dir) \
615         { \
616                 return blob_open(&table_name ## _table, \
617                         &table_name ## _table_desc, dir); \
618         }
619
620
621 /** Define the \p init function for this blob type. */
622 #define DEFINE_BLOB_INIT(table_name) \
623         void table_name ## _init(struct afs_table *t) \
624         { \
625                 t->open = table_name ## _open; \
626                 t->close = table_name ## _close; \
627                 t->create = table_name ## _create;\
628                 t->event_handler = table_name ##_event_handler; \
629                 table_name ## _table = NULL; \
630         }
631
632
633 /** Define all functions for this blob type. */
634 #define DEFINE_BLOB_FUNCTIONS(table_name, short_name, c_short_name) \
635         DEFINE_BLOB_OPEN(table_name) \
636         DEFINE_BLOB_CLOSE(table_name) \
637         DEFINE_BLOB_CREATE(table_name) \
638         DEFINE_BLOB_INIT(table_name) \
639         DEFINE_BLOB_COMMAND(ls, LS, table_name, short_name, c_short_name) \
640         DEFINE_BLOB_COMMAND(cat, CAT, table_name, short_name, c_short_name) \
641         DEFINE_BLOB_COMMAND(add, ADD, table_name, short_name, c_short_name) \
642         DEFINE_BLOB_COMMAND(rm, RM, table_name, short_name, c_short_name) \
643         DEFINE_BLOB_COMMAND(mv, MV, table_name, short_name, c_short_name) \
644         DEFINE_GET_NAME_BY_ID(table_name, short_name); \
645         DEFINE_GET_DEF_BY_ID(table_name, short_name); \
646         DEFINE_GET_DEF_BY_NAME(table_name, short_name); \
647         DEFINE_GET_NAME_AND_DEF_BY_ROW(table_name, short_name); \
648
649 /* doxygen isn't smart enough to recognize these */
650 /** \cond blob_function */
651 DEFINE_BLOB_FUNCTIONS(lyrics, lyr, LYR);
652 DEFINE_BLOB_FUNCTIONS(images, img, IMG);
653 DEFINE_BLOB_FUNCTIONS(moods, mood, MOOD);
654 DEFINE_BLOB_FUNCTIONS(playlists, pl, PL);
655 /** \endcond blob_function */