Fix some bugs in blob handling.
[paraslash.git] / blob.c
1 #include "para.h"
2 #include "error.h"
3 #include "afh.h"
4 #include "afs.h"
5 #include "string.h"
6 #include "net.h"
7
8 /** \file blob.c Macros and functions for blob handling. */
9
10 static struct osl_column_description blob_cols[] = {
11 [BLOBCOL_ID] = {
12 .storage_type = OSL_MAPPED_STORAGE,
13 .storage_flags = OSL_RBTREE | OSL_UNIQUE | OSL_FIXED_SIZE,
14 .name = "id",
15 .data_size = 4,
16 .compare_function = uint32_compare
17 },
18 [BLOBCOL_NAME] = {
19 .storage_type = OSL_MAPPED_STORAGE,
20 .storage_flags = OSL_RBTREE | OSL_UNIQUE,
21 .name = "name",
22 .compare_function = string_compare
23 },
24 [BLOBCOL_DEF] = {
25 .storage_type = OSL_DISK_STORAGE,
26 .storage_flags = 0,
27 .name = "definition"
28 }
29 };
30
31 /** \cond doxygen isn't smart enough to recognize these */
32 INIT_BLOB_TABLE(lyrics);
33 INIT_BLOB_TABLE(images);
34 INIT_BLOB_TABLE(moods);
35 INIT_BLOB_TABLE(playlists);
36 /** \endcond */
37
38 /** Flags that may be passed to the \p ls functions of each blob type. */
39 enum blob_ls_flags {
40 /** List both id and name. */
41 BLOB_LS_FLAG_LONG = 1,
42 /** Reverse sort order. */
43 BLOB_LS_FLAG_REVERSE = 2,
44 /** Sort by id instead of name. */
45 BLOB_LS_FLAG_SORT_BY_ID = 4,
46 };
47
48 /** Data passed to \p com_lsbob_callback(). */
49 struct com_lsblob_options {
50 /** Given flags for the ls command. */
51 uint32_t flags;
52 };
53
54 /** Structure passed to the \p print_blob loop function. */
55 struct lsblob_loop_data {
56 struct com_lsblob_options *opts;
57 struct para_buffer *pb;
58 struct osl_table *table;
59 };
60
61 static int print_blob(struct osl_row *row, void *loop_data)
62 {
63 struct osl_object obj;
64 char *name;
65 uint32_t id;
66 int ret;
67 struct lsblob_loop_data *lld = loop_data;
68
69 ret = osl_get_object(lld->table, row, BLOBCOL_NAME, &obj);
70 if (ret < 0)
71 return ret;
72 name = obj.data;
73 if (!*name) /* ignore dummy row */
74 return 1;
75 ret = osl_get_object(lld->table, row, BLOBCOL_ID, &obj);
76 if (ret < 0)
77 return ret;
78 id = *(uint32_t *)obj.data;
79 if (lld->opts->flags & BLOB_LS_FLAG_LONG)
80 para_printf(lld->pb, "%u\t%s\n", id, name);
81 else
82 para_printf(lld->pb, "%s\n", name);
83 return 1;
84 }
85
86 int com_lsblob_callback(struct osl_table *table,
87 const struct osl_object *query, struct osl_object *ls_output)
88 {
89 struct para_buffer pb = {.buf = NULL};
90 struct lsblob_loop_data lld = {.opts = query->data, .pb = &pb, .table = table};
91 int ret;
92
93 if (lld.opts->flags & BLOB_LS_FLAG_REVERSE) {
94 if (lld.opts->flags & BLOB_LS_FLAG_SORT_BY_ID)
95 ret = osl_rbtree_loop(lld.table, BLOBCOL_ID, &lld, print_blob);
96 else
97 ret = osl_rbtree_loop_reverse(lld.table, BLOBCOL_NAME, &lld, print_blob);
98 } else {
99 if (lld.opts->flags & BLOB_LS_FLAG_SORT_BY_ID)
100 ret = osl_rbtree_loop_reverse(lld.table, BLOBCOL_ID, &lld, print_blob);
101 else
102 ret = osl_rbtree_loop(lld.table, BLOBCOL_NAME, &lld, print_blob);
103 }
104 ls_output->data = pb.buf;
105 ls_output->size = pb.size;
106 return ret;
107 }
108
109 static int com_lsblob(callback_function *f, int fd, int argc, char * const * const argv)
110 {
111 struct com_lsblob_options clbo = {.flags = 0};
112 struct osl_object query = {.data = &clbo, .size = sizeof(clbo)},
113 ls_output;
114 int i, ret;
115
116 for (i = 1; i < argc; i++) {
117 const char *arg = argv[i];
118 if (arg[0] != '-')
119 break;
120 if (!strcmp(arg, "--")) {
121 i++;
122 break;
123 }
124 if (!strcmp(arg, "-l")) {
125 clbo.flags |= BLOB_LS_FLAG_LONG;
126 continue;
127 }
128 if (!strcmp(arg, "-i")) {
129 clbo.flags |= BLOB_LS_FLAG_SORT_BY_ID;
130 continue;
131 }
132 if (!strcmp(arg, "-r")) {
133 clbo.flags |= BLOB_LS_FLAG_REVERSE;
134 continue;
135 }
136 }
137 if (argc > i)
138 return -E_BLOB_SYNTAX;
139 ret = send_option_arg_callback_request(&query, argc - i,
140 argv + i, f, &ls_output);
141 if (ret > 0) {
142 send_buffer(fd, (char *)ls_output.data);
143 free(ls_output.data);
144 }
145 return ret;
146 }
147
148 static int com_catblob_callback(struct osl_table *table,
149 const struct osl_object *query, struct osl_object *output)
150 {
151 struct osl_object obj;
152 int ret;
153 struct osl_row *row;
154
155 ret = osl_get_row(table, BLOBCOL_NAME, query, &row);
156 if (ret < 0)
157 return ret;
158 ret = osl_open_disk_object(table, row, BLOBCOL_DEF, &obj);
159 if (ret < 0)
160 return ret;
161 output->data = para_malloc(obj.size);
162 output->size = obj.size;
163 memcpy(output->data, obj.data, obj.size);
164 return osl_close_disk_object(&obj);
165 }
166
167 static int com_catblob(callback_function *f, int fd, int argc,
168 char * const * const argv)
169 {
170 struct osl_object cat_output = {.data = NULL};
171 int ret;
172
173 if (argc != 2)
174 return -E_BLOB_SYNTAX;
175 if (!*argv[1]) /* empty name is reserved of the dummy row */
176 return -E_BLOB_SYNTAX;
177 ret = send_standard_callback_request(1, argv + 1, f, &cat_output);
178 if (ret > 0)
179 ret = send_bin_buffer(fd, (char *)cat_output.data, cat_output.size);
180 free(cat_output.data);
181 return ret;
182
183 }
184
185 static int com_addblob_callback(struct osl_table *table,
186 const struct osl_object *query,
187 __a_unused struct osl_object *result)
188 {
189 struct osl_object objs[NUM_BLOB_COLUMNS];
190 char *name = query->data;
191 size_t name_len = strlen(name) + 1;
192 uint32_t id;
193 unsigned num_rows;
194 int ret;
195
196 ret = osl_get_num_rows(table, &num_rows);
197 if (ret < 0)
198 return ret;
199 if (!num_rows) { /* this is the first entry ever added */
200 /* insert dummy row containing the id */
201 id = 2; /* this entry will be entry #1, so 2 is the next */
202 objs[BLOBCOL_ID].data = &id;
203 objs[BLOBCOL_ID].size = sizeof(id);
204 objs[BLOBCOL_NAME].data = "";
205 objs[BLOBCOL_NAME].size = 1;
206 objs[BLOBCOL_DEF].data = "";
207 objs[BLOBCOL_DEF].size = 1;
208 ret = osl_add_row(table, objs);
209 if (ret < 0)
210 return ret;
211 } else { /* get id of the dummy row and increment it */
212 struct osl_row *row;
213 struct osl_object obj = {.data = "", .size = 1};
214 ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row);
215 if (ret < 0)
216 return ret;
217 ret = osl_get_object(table, row, BLOBCOL_ID, &obj);
218 if (ret < 0)
219 return ret;
220 id = *(uint32_t *)obj.data + 1;
221 obj.data = &id;
222 ret = osl_update_object(table, row, BLOBCOL_ID, &obj);
223 if (ret < 0)
224 return ret;
225 }
226 id--;
227 objs[BLOBCOL_ID].data = &id;
228 objs[BLOBCOL_ID].size = sizeof(id);
229 objs[BLOBCOL_NAME].data = name;
230 objs[BLOBCOL_NAME].size = name_len;
231 objs[BLOBCOL_DEF].data = name + name_len;
232 objs[BLOBCOL_DEF].size = query->size - name_len;
233 return osl_add_row(table, objs);
234 }
235
236 static int com_addblob(callback_function *f, int fd, int argc,
237 char * const * const argv)
238 {
239 struct osl_object arg_obj;
240
241 if (argc != 2)
242 return -E_BLOB_SYNTAX;
243 if (!*argv[1]) /* empty name is reserved for the dummy row */
244 return -E_BLOB_SYNTAX;
245 PARA_NOTICE_LOG("argv[1]: %s\n", argv[1]);
246 arg_obj.size = strlen(argv[1]) + 1;
247 arg_obj.data = (char *)argv[1];
248 return stdin_command(fd, &arg_obj, f, 10 * 1024 * 1024, NULL);
249 }
250
251 static int com_rmblob_callback(struct osl_table *table,
252 const struct osl_object *query,
253 __a_unused struct osl_object *result)
254 {
255 char *p = query->data;
256 size_t len;
257 int ret;
258
259 for (; p < (char *)query->data + query->size; p += len + 1) {
260 struct osl_row *row;
261 struct osl_object obj;
262
263 len = strlen(p);
264 obj.data = p;
265 obj.size = len + 1;
266 ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row);
267 if (ret < 0)
268 return ret;
269 ret = osl_del_row(table, row);
270 if (ret < 0)
271 return ret;
272 }
273 return 1;
274 }
275
276 static int com_rmblob(callback_function *f, __a_unused int fd, int argc,
277 char * const * const argv)
278 {
279 if (argc < 2)
280 return -E_MOOD_SYNTAX;
281 return send_option_arg_callback_request(NULL, argc - 1, argv + 1, f,
282 NULL);
283 }
284
285 static int com_mvblob_callback(struct osl_table *table,
286 const struct osl_object *query,
287 __a_unused struct osl_object *result)
288 {
289 char *src = (char *) query->data;
290 struct osl_object obj = {.data = src, .size = strlen(src) + 1};
291 char *dest = src + obj.size;
292 struct osl_row *row;
293 int ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row);
294
295 if (ret < 0)
296 return ret;
297 obj.data = dest;
298 obj.size = strlen(dest) + 1;
299 return osl_update_object(table, row, BLOBCOL_NAME, &obj);
300 }
301
302 static int com_mvblob(callback_function *f, __a_unused int fd,
303 int argc, char * const * const argv)
304 {
305 if (argc != 3)
306 return -E_MOOD_SYNTAX;
307 return send_option_arg_callback_request(NULL, argc - 1, argv + 1, f,
308 NULL);
309 }
310
311 #define DEFINE_BLOB_COMMAND(cmd_name, table_name, cmd_prefix) \
312 static int com_ ## cmd_name ## cmd_prefix ## _callback(const struct osl_object *query, \
313 struct osl_object *output) \
314 { \
315 return com_ ## cmd_name ## blob_callback(table_name ## _table, query, output); \
316 } \
317 int com_ ## cmd_name ## cmd_prefix(int fd, int argc, char * const * const argv) \
318 { \
319 return com_ ## cmd_name ## blob(com_ ## cmd_name ## cmd_prefix ## _callback, fd, argc, argv); \
320 }
321
322 static int blob_get_name_by_id(struct osl_table *table, uint32_t id,
323 char **name)
324 {
325 struct osl_row *row;
326 struct osl_object obj = {.data = &id, .size = sizeof(id)};
327 int ret;
328
329 *name = NULL;
330 if (!id)
331 return 1;
332 ret = osl_get_row(table, BLOBCOL_ID, &obj, &row);
333 if (ret < 0)
334 return ret;
335 ret = osl_get_object(table, row, BLOBCOL_NAME, &obj);
336 if (ret < 0)
337 return ret;
338 *name = (char *)obj.data;
339 return 1;
340 }
341 /** Define the \p get_name_by_id function for this blob type. */
342 #define DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix) \
343 int cmd_prefix ## _get_name_by_id(uint32_t id, char **name) \
344 { \
345 return blob_get_name_by_id(table_name ## _table, id, name); \
346 }
347
348 /** Define the \p shutdown function for this blob type. */
349 #define DEFINE_BLOB_SHUTDOWN(table_name) \
350 void table_name ## _shutdown(enum osl_close_flags flags) \
351 { \
352 osl_close_table(table_name ## _table, flags); \
353 table_name ## _table = NULL; \
354 }
355
356 static int blob_init(struct osl_table **table,
357 struct osl_table_description *desc,
358 struct table_info *ti, const char *db)
359 {
360 int ret;
361 desc->dir = db;
362 ti->desc = desc;
363 ret = osl_open_table(ti->desc, &ti->table);
364 if (ret >= 0) {
365 *table = ti->table;
366 return ret;
367 }
368 *table = NULL;
369 return ret == -E_NOENT? 1 : ret;
370 }
371
372 /** Define the \p init function for this blob type. */
373 #define DEFINE_BLOB_INIT(table_name) \
374 int table_name ## _init(struct table_info *ti, const char *db) \
375 { \
376 return blob_init(&table_name ## _table, \
377 &table_name ## _table_desc, ti, db); \
378 }
379
380
381 /** Define all functions for this blob type. */
382 #define DEFINE_BLOB_FUNCTIONS(table_name, cmd_prefix) \
383 DEFINE_BLOB_COMMAND(ls, table_name, cmd_prefix) \
384 DEFINE_BLOB_COMMAND(cat, table_name, cmd_prefix) \
385 DEFINE_BLOB_COMMAND(add, table_name, cmd_prefix) \
386 DEFINE_BLOB_COMMAND(rm, table_name, cmd_prefix) \
387 DEFINE_BLOB_COMMAND(mv, table_name, cmd_prefix) \
388 DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix); \
389 DEFINE_BLOB_SHUTDOWN(table_name); \
390 DEFINE_BLOB_INIT(table_name);
391
392 /** \cond doxygen isn't smart enough to recognize these */
393 DEFINE_BLOB_FUNCTIONS(lyrics, lyr);
394 DEFINE_BLOB_FUNCTIONS(images, img);
395 DEFINE_BLOB_FUNCTIONS(moods, mood);
396 DEFINE_BLOB_FUNCTIONS(playlists, pl);
397 /** \endcond */