para_malloc(), para_realloc(): Check for zero size allocations.
[paraslash.git] / mysql_selector.c
1 /*
2  * Copyright (C) 1999-2007 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file mysql_selector.c para_server's mysql-based audio file selector */
8
9 /** \cond some internal constants */
10 #define MEDIUM_BLOB_SIZE 16777220 /*  (2**24 + 4) */
11 #define BLOB_SIZE 65539 /* (2**16 + 3) */
12 /** \endcond */
13 #include "server.cmdline.h"
14 #include "server.h"
15 #include "vss.h"
16 #include "afs.h"
17 #include <mysql/mysql.h>
18 #include <mysql/mysql_version.h>
19 #include <regex.h>
20 #include "error.h"
21 #include "net.h"
22 #include "string.h"
23 #include "user_list.h"
24 #include "mysql_selector_command_list.h"
25 #include "ipc.h"
26
27 /** pointer to the shared memory area */
28 extern struct misc_meta_data *mmd;
29
30 static void *mysql_ptr = NULL;
31 static int mysql_lock;
32
33 /**
34  * contains name/replacement pairs used by s_a_r_list()
35  *
36  * \sa s_a_r()
37  */
38 struct para_macro {
39         /** the name of the macro */
40         const char *name;
41         /** the replacement text */
42         const char *replacement;
43 };
44
45 static const struct para_macro mysql_macro_list[] = {
46         {       .name = "IS_N_SET",
47                 .replacement = "(data.%s != '1')"
48         }, {
49                 .name = "IS_SET",
50                 .replacement = "(data.%s = '1')"
51         }, {
52                 .name = "PICID",
53                 .replacement = "%sdata.Pic_Id"
54         }, {
55                 .name = "NAME_LIKE",
56                 .replacement = "(data.name like '%s')"
57         }, {
58                 .name = "LASTPLAYED",
59                 .replacement = "%sFLOOR((UNIX_TIMESTAMP(now())"
60                         "-UNIX_TIMESTAMP(data.Lastplayed))/60)"
61         }, {
62                 .name = "NUMPLAYED",
63                 .replacement = "%sdata.Numplayed"
64         }, {
65                 .name = NULL,
66         }
67 };
68
69 /**
70  * simple search and replace routine
71  *
72  * \param src source string
73  * \param macro_name the name of the macro
74  * \param replacement the replacement format string
75  *
76  * In \p src, replace each occurence of \p macro_name(arg) by the string
77  * determined by the \p replacement format string. \p replacement may (but
78  * needs not) contain a single string conversion specifier (%s) which gets
79  * replaced by \p arg.
80  *
81  * \return A string in which all matches in \p src are replaced, or \p NULL if
82  * an syntax error was encountered. Caller must free the result.
83  *
84  * \sa regcomp(3)
85  */
86 __must_check __malloc static char *s_a_r(const char *src, const char* macro_name,
87                 const char *replacement)
88 {
89         regex_t preg;
90         size_t  nmatch = 1;
91         regmatch_t pmatch[1];
92         int eflags = 0;
93         char *dest = NULL;
94         const char *bufptr = src;
95
96         if (!macro_name || !replacement || !src)
97                 return para_strdup(src);
98         regcomp(&preg, macro_name, 0);
99         while (regexec(&preg,  bufptr, nmatch, pmatch, eflags)
100                         != REG_NOMATCH) {
101                 char *tmp, *arg, *o_bracket, *c_bracket;
102
103                 o_bracket = strchr(bufptr + pmatch[0].rm_so, '(');
104                 c_bracket = o_bracket? strchr(o_bracket, ')') : NULL;
105                 if (!c_bracket)
106                         goto out;
107                 tmp = para_strdup(bufptr);
108                 tmp[pmatch[0].rm_so] = '\0';
109                 dest = para_strcat(dest, tmp);
110                 free(tmp);
111
112                 arg = para_strdup(o_bracket + 1);
113                 arg[c_bracket - o_bracket - 1] = '\0';
114                 tmp = make_message(replacement, arg);
115                 free(arg);
116                 dest = para_strcat(dest, tmp);
117                 free(tmp);
118                 bufptr = c_bracket;
119                 bufptr++;
120         }
121         dest = para_strcat(dest, bufptr);
122 //      PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
123 out:
124         regfree(&preg);
125         return dest;
126 }
127
128 /**
129  * replace a string according to a list of macros
130  *
131  * \param macro_list the array containing a macro/replacement pairs.
132  * \param src the source string
133  *
134  * This function just calls s_a_r() for each element of \p macro_list.
135  *
136  * \return \p NULL if one of the underlying calls to \p s_a_r returned \p NULL.
137  * Otherwise the completely expanded version of \p src is returned.
138  */
139 __must_check __malloc static char *s_a_r_list(const struct para_macro *macro_list,
140                 char *src)
141 {
142         const struct para_macro *mp = macro_list;
143         char *ret = NULL, *tmp = para_strdup(src);
144
145         while (mp->name) {
146                 ret = s_a_r(tmp, mp->name, mp->replacement);
147                 free(tmp);
148                 if (!ret) /* syntax error */
149                         return NULL;
150                 tmp = ret;
151                 mp++;
152         }
153         //PARA_DEBUG_LOG("%s: returning %s\n", __func__, dest);
154         return ret;
155 }
156
157 static int lockless_real_query(const char *query)
158 {
159         if (!mysql_ptr)
160                 return -E_NOTCONN;
161         PARA_DEBUG_LOG("%s\n", query);
162         if (mysql_real_query(mysql_ptr, query, strlen(query))) {
163                 PARA_ERROR_LOG("real_query error (%s)\n",
164                         mysql_error(mysql_ptr));
165                 return -E_QFAILED;
166         }
167         return 1;
168 }
169
170 static int real_query(const char *query)
171 {
172         int ret;
173
174         mutex_lock(mysql_lock);
175         ret = lockless_real_query(query);
176         mutex_unlock(mysql_lock);
177         return ret;
178 }
179
180 /*
181  * Use open connection given by mysql_ptr to query server. Returns a
182  * result pointer on succes and NULL on errors
183  */
184 static struct MYSQL_RES *get_result(const char *query)
185 {
186         void *result = NULL;
187
188         mutex_lock(mysql_lock);
189         if (lockless_real_query(query) < 0)
190                 goto out;
191         result = mysql_store_result(mysql_ptr);
192         if (!result)
193                 PARA_ERROR_LOG("%s", "store_result error\n");
194 out:
195         mutex_unlock(mysql_lock);
196         return result;
197 }
198 /*
199  * write input from fd to dynamically allocated char array,
200  * but maximal max_size byte. Return size.
201  */
202 static int fd2buf(int fd, char **buf_ptr, size_t max_size)
203 {
204         const size_t chunk_size = 1024;
205         size_t size = 2048;
206         char *buf = para_malloc(size * sizeof(char)), *p = buf;
207         int ret;
208
209         while ((ret = recv_bin_buffer(fd, p, chunk_size)) > 0) {
210                 p += ret;
211                 if ((p - buf) + chunk_size >= size) {
212                         char *tmp;
213
214                         size *= 2;
215                         if (size > max_size) {
216                                 ret = -E_TOOBIG;
217                                 goto out;
218                         }
219                         tmp = para_realloc(buf, size);
220                         p = (p - buf) + tmp;
221                         buf = tmp;
222                 }
223         }
224         if (ret < 0)
225                 goto out;
226         *buf_ptr = buf;
227         ret = p - buf;
228 out:
229         if (ret < 0 && buf)
230                 free(buf);
231         return ret;
232 }
233
234 static char *escape_blob(const char* old, size_t size)
235 {
236         char *new;
237
238         if (!mysql_ptr)
239                 return NULL;
240         new = para_malloc(2 * size * sizeof(char) + 1);
241         mysql_real_escape_string(mysql_ptr, new, old, size);
242         return new;
243 }
244
245 static char *escape_str(const char* old)
246 {
247         return escape_blob(old, strlen(old));
248 }
249
250 static char *escaped_basename(const char *name)
251 {
252         char *esc, *bn = para_basename(name);
253
254         if (!bn)
255                 return NULL;
256         esc = escape_str(bn);
257         free(bn);
258         return esc;
259 }
260
261 /*
262  * new attribute
263  */
264 int com_na(__a_unused int fd, int argc, char *argv[])
265 {
266         char *q, *tmp;
267         int ret;
268
269         if (argc < 2)
270                 return -E_MYSQL_SYNTAX;
271         tmp = escape_str(argv[1]);
272         if (!tmp)
273                 return -E_ESCAPE;
274         q = make_message("alter table data add %s char(1) "
275                 "not null default 0", tmp);
276         free(tmp);
277         ret = real_query(q);
278         free(q);
279         return ret;
280 }
281
282 /*
283  * delete attribute
284  */
285 int com_da(__a_unused int fd, int argc, char *argv[])
286 {
287         char *q, *tmp;
288         int ret;
289
290         if (argc < 2)
291                 return -E_MYSQL_SYNTAX;
292         tmp = escape_str(argv[1]);
293         if (!tmp)
294                 return -E_ESCAPE;
295         q = make_message("alter table data drop %s", tmp);
296         free(tmp);
297         ret = real_query(q);
298         free(q);
299         return ret;
300 }
301
302 /* stradd/pic_add */
303 static int com_stradd_picadd(int fd, int argc, char *argv[])
304 {
305         char *blob = NULL, *esc_blob = NULL, *q = NULL, *tmp = NULL;
306         const char *fmt, *del_fmt;
307         int ret, stradd = strcmp(argv[0], "picadd");
308         size_t size;
309
310         if (argc < 2)
311                 return -E_MYSQL_SYNTAX;
312         if (strlen(argv[1]) >= MAXLINE - 1)
313                 return -E_NAMETOOLONG;
314         if (!mysql_ptr)
315                 return -E_NOTCONN;
316         if (stradd) {
317                 size = BLOB_SIZE;
318                 fmt = "insert into streams (name, def) values ('%s','%s')";
319                 del_fmt="delete from streams where name='%s'";
320         } else {
321                 size = MEDIUM_BLOB_SIZE;
322                 fmt = "insert into pics (name, pic) values ('%s','%s')";
323                 del_fmt="delete from pics where pic='%s'";
324         }
325         tmp = escape_str(argv[1]);
326         if (!tmp)
327                 return -E_ESCAPE;
328         q = make_message(del_fmt, tmp);
329         free(tmp);
330         ret = real_query(q);
331         free(q);
332         if (ret < 0)
333                 return ret;
334         if ((ret = send_buffer(fd, AWAITING_DATA_MSG) < 0))
335                 return ret;
336         if ((ret = fd2buf(fd, &blob, size)) < 0)
337                 return ret;
338         size = ret;
339         if (stradd)
340                 blob[size] = '\0';
341         ret = -E_ESCAPE;
342         esc_blob = escape_blob(blob, size);
343         if (!esc_blob)
344                 goto out;
345         tmp = escape_str(argv[1]);
346         if (!tmp)
347                 goto out;
348         q = make_message(fmt, tmp, esc_blob);
349         ret = real_query(q);
350 out:
351         free(blob);
352         free(esc_blob);
353         free(tmp);
354         free(q);
355         return ret;
356 }
357
358 /* stradd */
359 int com_stradd(int fd, int argc, char *argv[])
360 {
361         return com_stradd_picadd(fd, argc, argv);
362 }
363
364 /* pic_add */
365 int com_picadd(int fd, int argc, char *argv[])
366 {
367         return com_stradd_picadd(fd, argc, argv);
368 }
369
370 /*
371  * print results to fd
372  */
373 static int print_results(int fd, void *result,
374                 my_ulonglong top, my_ulonglong left,
375                 my_ulonglong bottom, my_ulonglong right)
376 {
377         unsigned int i,j;
378         int ret;
379         MYSQL_ROW row;
380
381         for (i = top; i <= bottom; i++) {
382                 row = mysql_fetch_row(result);
383                 if (!row || !row[0])
384                         return -E_NOROW;
385                 for (j = left; j <= right; j++) {
386                         ret = send_va_buffer(fd, j == left? "%s" : "\t%s",
387                                         row[j]?  row[j] : "NULL");
388                         if (ret < 0)
389                                 return ret;
390                 }
391                 ret = send_buffer(fd, "\n");
392                 if (ret < 0)
393                         return ret;
394         }
395         return 0;
396 }
397
398 /*
399  * verbatim
400  */
401 int com_verb(int fd, int argc, char *argv[])
402 {
403         void *result = NULL;
404         int ret;
405         my_ulonglong num_rows, num_fields, top = 0, left = 0;
406         char *tmp;
407
408         if (argc < 2)
409                 return -E_MYSQL_SYNTAX;
410         tmp = escape_str(argv[1]);
411         if (!tmp)
412                 return -E_ESCAPE;
413         result = get_result(tmp);
414         free(tmp);
415         if (!result)
416                 /* return success, because it's ok to have no results */
417                 return 1;
418         num_fields = mysql_field_count(mysql_ptr);
419         num_rows = mysql_num_rows(result);
420         ret = 1;
421         if (num_fields && num_rows)
422                 ret = print_results(fd, result, top, left, num_rows - 1,
423                         num_fields - 1);
424         mysql_free_result(result);
425         return ret;
426 }
427
428 /* returns NULL on errors or if there are no atts defined yet */
429 static void *get_all_attributes(void)
430 {
431         void *result = get_result("desc data");
432         unsigned int num_rows;
433
434         if (!result)
435                 return NULL;
436         num_rows = mysql_num_rows(result);
437         if (num_rows < 5) {
438                 mysql_free_result(result);
439                 return NULL;
440         }
441         mysql_data_seek(result, (my_ulonglong)4); /* skip Lastplayed, Numplayed... */
442         return result;
443 }
444
445 /*
446  * list all attributes
447  */
448 int com_laa(int fd, int argc, __a_unused char *argv[])
449 {
450         void *result;
451         int ret;
452         my_ulonglong top = 0, left = 0, bottom, right = 0;
453
454         if (argc != 1)
455                 return -E_MYSQL_SYNTAX;
456         result = get_all_attributes();
457         if (!result)
458                 return -E_NOATTS;
459         bottom = mysql_num_rows(result);
460         if (bottom < 5)
461                 return -E_MYSQL_SYNTAX;
462         bottom -= 5;
463         ret = print_results(fd, result, top, left, bottom, right);
464         mysql_free_result(result);
465         return ret;
466 }
467
468 /*
469  * history
470  */
471 int com_hist(int fd, int argc, char *argv[])
472 {
473         int ret;
474         void *result = NULL;
475         char *q, *atts;
476         my_ulonglong num_rows, top = 0, left = 0, right = 1;
477
478         if (argc > 3)
479                 return -E_MYSQL_SYNTAX;
480         if (argc > 1) {
481                 char *tmp = escape_str(argv[1]);
482                 if (!tmp)
483                         return -E_ESCAPE;
484                 atts = make_message("where %s = '1'", tmp);
485                 free(tmp);
486         } else
487                 atts = para_strdup(NULL);
488
489         q = make_message("select name, to_days(now()) - to_days(lastplayed) from "
490                 "data %s order by lastplayed", atts);
491         free(atts);
492         result = get_result(q);
493         free(q);
494         if (!result)
495                 return -E_NORESULT;
496         num_rows = mysql_num_rows(result);
497         ret = 1;
498         if (num_rows)
499                 ret = print_results(fd, result, top, left, num_rows - 1, right);
500         mysql_free_result(result);
501         return ret;
502 }
503
504 /*
505  * get last num audio files
506  */
507 int com_last(int fd, int argc, char *argv[])
508 {
509         void *result = NULL;
510         char *q;
511         int num, ret;
512         my_ulonglong top = 0, left = 0, right = 0;
513
514         if (argc < 2)
515                 num = 10;
516         else
517                 num = atoi(argv[1]);
518         if (!num)
519                 return -E_MYSQL_SYNTAX;
520         q = make_message("select name from data order by lastplayed desc "
521                 "limit %u", num);
522         result = get_result(q);
523         free(q);
524         if (!result)
525                 return -E_NORESULT;
526         ret = print_results(fd, result, top, left, mysql_num_rows(result) - 1,
527                 right);
528         mysql_free_result(result);
529         return ret;
530 }
531
532 int com_mbox(int fd, int argc, char *argv[])
533 {
534         void *result;
535         MYSQL_ROW row;
536         int ret;
537         my_ulonglong num_rows, num_fields, top = 0, left = 0;
538         char *query = para_strdup("select concat('From foo@localhost ', "
539                 "date_format(Lastplayed, '%a %b %e %T %Y'), "
540                 "'\nReceived: from\nTo: bar\n");
541
542         ret = -E_NOATTS;
543         result = get_all_attributes();
544         if (!result)
545                 goto out;
546         ret = -E_NOROW;
547         while ((row = mysql_fetch_row(result))) {
548                 char *tmp;
549
550                 if (!row[0])
551                         goto out;
552                 tmp = make_message("%sX-Attribute-%s: ', %s, '\n", query,
553                         row[0], row[0]);
554                 free(query);
555                 query = tmp;
556         }
557         query = para_strcat(query,
558                 "From: a\n"
559                 "Subject: "
560                 "', name, '"
561                 "\n\n\n"
562                 "') from data"
563         );
564         if (argc >= 2) {
565                 char *esc = escape_str(argv[1]), *tmp;
566                 ret = -E_ESCAPE;
567                 if (!esc)
568                         goto out;
569                 tmp = make_message("%s where name LIKE '%s'", query, esc);
570                 free(esc);
571                 free(query);
572                 query = tmp;
573         }
574         mysql_free_result(result);
575         ret = -E_NORESULT;
576         result = get_result(query);
577         if (!result)
578                 goto out;
579         ret = -E_EMPTY_RESULT;
580         num_fields = mysql_field_count(mysql_ptr);
581         num_rows = mysql_num_rows(result);
582         if (!num_fields || !num_rows)
583                 goto out;
584         ret = print_results(fd, result, top, left, num_rows - 1,
585                 num_fields - 1);
586 out:
587         free(query);
588         if (result)
589                 mysql_free_result(result);
590         return ret;
591 }
592
593 /*
594  * get attributes by name. If verbose is not 0, this function returns a string
595  * of the form 'att1="0",att2="1"'... which is used in com_cam() for
596  * constructing a mysql update query. Otherwise the space-separated list of all
597  * attributes which are set in the audio file given by name is returned.  Never
598  * returns NULL in *NON VERBOSE* mode.
599  */
600 static char *get_atts(char *name, int verbose)
601 {
602         char *atts = NULL, *buf, *ebn;
603         void *result = NULL, *result2 = NULL;
604         MYSQL_ROW row, row2;
605         int i;
606         my_ulonglong num_fields, offset = 4; /* skip Lastplayed, Numplayed... */
607
608
609         result2 = get_all_attributes();
610         if (!result2)
611                 goto out;
612         ebn = escaped_basename(name);
613         if (!ebn)
614                 goto out;
615         buf = make_message("select * from data where name='%s'", ebn);
616         free(ebn);
617         result = get_result(buf);
618         free(buf);
619         if (!result)
620                 goto out;
621         num_fields = mysql_num_fields(result);
622         if (num_fields < 5)
623                 goto out;
624         mysql_data_seek(result2, offset);
625         row = mysql_fetch_row(result);
626         if (!row)
627                 goto out;
628         for (i = 4; i < num_fields; i++) {
629                 int is_set = row[i] && !strcmp(row[i], "1");
630                 row2 = mysql_fetch_row(result2);
631                 if (!row2 || !row2[0])
632                         goto out;
633                 if (atts && (verbose || is_set))
634                         atts = para_strcat(atts, verbose? "," : " ");
635                 if (is_set || verbose)
636                         atts = para_strcat(atts, row2[0]);
637                 if (verbose)
638                         atts = para_strcat(atts, is_set? "=\"1\"" : "=\"0\"");
639         }
640 out:
641         if (result2)
642                 mysql_free_result(result2);
643         if (result)
644                 mysql_free_result(result);
645         if (!atts && !verbose)
646                 atts = para_strdup("(none)");
647         return atts;
648 }
649
650 /* never returns NULL in verbose mode */
651 static char *get_meta(char *name, int verbose)
652 {
653         MYSQL_ROW row;
654         void *result = NULL;
655         char *ebn, *q, *ret = NULL;
656         const char *verbose_fmt =
657                 "select concat('lastplayed: ', "
658                 "(to_days(now()) - to_days(lastplayed)),"
659                 "' day(s). numplayed: ', numplayed, "
660                 "', pic: ', pic_id) "
661                 "from data where name = '%s'";
662         /* is that really needed? */
663         const char *fmt = "select concat('lastplayed=\\'', lastplayed, "
664                 "'\\', numplayed=\\'', numplayed, "
665                 "'\\', pic_id=\\'', pic_id, '\\'') "
666                 "from data where name = '%s'";
667
668         if (!(ebn = escaped_basename(name)))
669                 goto out;
670         q = make_message(verbose? verbose_fmt : fmt, ebn);
671         free(ebn);
672         result = get_result(q);
673         free(q);
674         if (!result)
675                 goto out;
676         row = mysql_fetch_row(result);
677         if (!row || !row[0])
678                 goto out;
679         ret = para_strdup(row[0]);
680 out:
681         if (result)
682                 mysql_free_result(result);
683         if (!ret && verbose)
684                 ret = para_strdup("(not yet played)");
685         return ret;
686 }
687
688 static char *get_dir(char *name)
689 {
690         char *ret = NULL, *q, *ebn;
691         void *result;
692         MYSQL_ROW row;
693
694         if (!(ebn = escaped_basename(name)))
695                 return NULL;
696         q = make_message("select dir from dir where name = '%s'", ebn);
697         free(ebn);
698         result = get_result(q);
699         free(q);
700         if (!result)
701                 return NULL;
702         row = mysql_fetch_row(result);
703         if (row && row[0])
704                 ret = para_strdup(row[0]);
705         mysql_free_result(result);
706         return ret;
707 }
708
709 /* never returns NULL */
710 static char *get_current_stream(void)
711 {
712         char *ret;
713         MYSQL_ROW row;
714         void *result = get_result("select def from streams where "
715                 "name = 'current_stream'");
716
717         if (!result)
718                 goto err_out;
719         row = mysql_fetch_row(result);
720         if (!row || !row[0])
721                 goto err_out;
722         ret = para_strdup(row[0]);
723         mysql_free_result(result);
724         return ret;
725 err_out:
726         if (result)
727                 mysql_free_result(result);
728         return para_strdup("(none)");
729 }
730
731 /*
732  * Read stream definition of stream streamname and construct mysql
733  * query. Return NULL on errors. If streamname is NULL, use current
734  * stream. If that is also NULL, use query that selects everything.
735  * If filename is NULL, query will list everything, otherwise only
736  * the score of given file.
737  */
738 static char *get_query(char *streamname, char *filename, int with_path)
739 {
740         char *accept_opts = NULL, *deny_opts = NULL, *score = NULL;
741         char *where_clause, *order, *query;
742         char command[255] = ""; /* buffer for sscanf */
743         void *result;
744         MYSQL_ROW row;
745         char *end, *tmp;
746         char *select_clause = NULL;
747         if (!streamname)
748                 tmp = get_current_stream();
749         else {
750                 tmp = escape_str(streamname);
751                 if (!tmp)
752                         return NULL;
753         }
754         if (!strcmp(tmp, "(none)")) {
755                 free(tmp);
756                 if (filename) {
757                         char *ret, *ebn = escaped_basename(filename);
758                         if (!ebn)
759                                 return NULL;
760                         ret = make_message("select to_days(now()) - "
761                                 "to_days(lastplayed) from data "
762                                 "where name = '%s'", ebn);
763                         free(ebn);
764                         return ret;
765                 }
766                 if (with_path)
767                         return make_message(
768                                 "select concat(dir.dir, '/', dir.name) "
769                                 "from data, dir where dir.name = data.name "
770                                 "order by data.lastplayed"
771                         );
772                 return make_message(
773                         "select name from data where name is not NULL "
774                         "order by lastplayed"
775                 );
776         }
777         free(tmp);
778         query = make_message("select def from streams where name = '%s'",
779                 streamname);
780         result = get_result(query);
781         free(query);
782         query = NULL;
783         if (!result)
784                 goto out;
785         row = mysql_fetch_row(result);
786         if (!row || !row[0])
787                 goto out;
788         end = row[0];
789         while (*end) {
790                 int n;
791                 char *arg, *line = end;
792
793                 if (!(end = strchr(line, '\n')))
794                         break;
795                 *end = '\0';
796                 end++;
797                 if (sscanf(line, "%200s%n", command, &n) < 1)
798                         continue;
799                 arg = line + n;
800                 if (!strcmp(command, "accept:")) {
801                         char *tmp2 = s_a_r_list(mysql_macro_list, arg);
802                         if (accept_opts)
803                                 accept_opts = para_strcat(
804                                         accept_opts, " or ");
805                         accept_opts = para_strcat(accept_opts, tmp2);
806                         free(tmp2);
807                         continue;
808                 }
809                 if (!strcmp(command, "deny:")) {
810                         char *tmp2 = s_a_r_list(mysql_macro_list, arg);
811                         if (deny_opts)
812                                 deny_opts = para_strcat(deny_opts, " or ");
813                         deny_opts = para_strcat(deny_opts, tmp2);
814                         free(tmp2);
815                         continue;
816                 }
817                 if (!score && !strcmp(command, "score:"))
818                         score = s_a_r_list(mysql_macro_list, arg);
819         }
820         if (!score) {
821                 score = s_a_r_list(mysql_macro_list, conf.mysql_default_score_arg);
822                 if (!score)
823                         goto out;
824         }
825         if (filename) {
826                 char *ebn = escaped_basename(filename);
827                 if (!ebn)
828                         goto out;
829                 select_clause = make_message("select %s from data ", score);
830                 free(score);
831                 where_clause = make_message( "where name = '%s' ", ebn);
832                 free(ebn);
833                 order = para_strdup("");
834                 goto write_query;
835         }
836         select_clause = para_strdup(with_path?
837                 "select concat(dir.dir, '/', dir.name) from data, dir "
838                 "where dir.name = data.name "
839                 :
840                 "select name from data where name is not NULL");
841         order = make_message("order by -(%s)", score);
842         free(score);
843         if (accept_opts && deny_opts) {
844                 where_clause = make_message("and ((%s) and not (%s)) ",
845                         accept_opts, deny_opts);
846                 goto write_query;
847         }
848         if (accept_opts && !deny_opts) {
849                 where_clause = make_message("and (%s) ", accept_opts);
850                 goto write_query;
851         }
852         if (!accept_opts && deny_opts) {
853                 where_clause = make_message("and not (%s) ", deny_opts);
854                 goto write_query;
855         }
856         where_clause = para_strdup("");
857 write_query:
858         query = make_message("%s %s %s", select_clause, where_clause, order);
859         free(order);
860         free(select_clause);
861         free(where_clause);
862 out:
863         if (accept_opts)
864                 free(accept_opts);
865         if (deny_opts)
866                 free(deny_opts);
867         if (result)
868                 mysql_free_result(result);
869         return query;
870 }
871
872
873
874 /*
875  * This is called from server and from some commands. Name must not be NULL
876  * Never returns NULL.
877  */
878 static char *get_selector_info(char *name)
879 {
880         char *meta, *atts, *info, *dir, *query, *stream;
881         void *result = NULL;
882         MYSQL_ROW row = NULL;
883
884         if (!name)
885                 return para_strdup("(none)");
886         stream = get_current_stream();
887         meta = get_meta(name, 1);
888         atts = get_atts(name, 0);
889         dir = get_dir(name);
890         /* get score */
891         query = get_query(stream, name, 0); /* FIXME: pass stream == NULL instead? */
892         if (!query)
893                 goto write;
894         result = get_result(query);
895         free(query);
896         if (result)
897                 row = mysql_fetch_row(result);
898 write:
899         info = make_message("dbinfo1:dir: %s\n"
900                 "dbinfo2:stream: %s, %s, score: %s\n"
901                 "dbinfo3:%s\n",
902                 dir? dir : "(not contained in table)",
903                 stream, meta, 
904                 (result && row && row[0])? row[0] : "(no score)",
905                 atts);
906         free(dir);
907         free(meta);
908         free(atts);
909         free(stream);
910         if (result)
911                 mysql_free_result(result);
912         return info;
913 }
914
915 /* might return NULL */
916 static char *get_current_audio_file(void)
917 {
918         char *name;
919         mmd_lock();
920         name = para_basename(mmd->filename);
921         mmd_unlock();
922         return name;
923 }
924
925 /* If called as child, mmd_lock must be held */
926 static void update_mmd(char *info)
927 {
928         PARA_DEBUG_LOG("%s", "updating shared memory area\n");
929         strncpy(mmd->selector_info, info, MMD_INFO_SIZE - 1);
930         mmd->selector_info[MMD_INFO_SIZE - 1] = '\0';
931 }
932
933 static void refresh_selector_info(void)
934 {
935         char *name = get_current_audio_file();
936         char *info;
937
938         if (!name)
939                 return;
940         info = get_selector_info(name);
941         free(name);
942         mmd_lock();
943         update_mmd(info);
944         mmd_unlock();
945         free(info);
946 }
947
948 /* list attributes / print database info */
949 static int com_la_info(int fd, int argc, char *argv[])
950 {
951         char *name = NULL, *meta = NULL, *atts = NULL, *dir = NULL;
952         int ret, la = strcmp(argv[0], "info");
953
954         if (argc < 2) {
955                 ret = -E_GET_AUDIO_FILE;
956                 if (!(name = get_current_audio_file()))
957                         goto out;
958                 ret = send_va_buffer(fd, "%s\n", name);
959                 if (ret < 0)
960                         goto out;
961         } else {
962                 ret = -E_ESCAPE;
963                 if (!(name = escaped_basename(argv[1])))
964                         goto out;
965         }
966         meta = get_meta(name, 1);
967         atts = get_atts(name, 0);
968         dir = get_dir(name);
969         if (la)
970                 ret = send_va_buffer(fd, "%s\n", atts);
971         else
972                 ret = send_va_buffer(fd, "dir: %s\n" "%s\n" "attributes: %s\n",
973                         dir? dir : "(not contained in table)", meta, atts);
974 out:
975         free(meta);
976         free(atts);
977         free(dir);
978         free(name);
979         return ret;
980 }
981
982 /* list attributes */
983 int com_la(int fd, int argc, char *argv[])
984 {
985         return com_la_info(fd, argc, argv);
986 }
987
988 /* print database info */
989 int com_info(int fd, int argc, char *argv[])
990 {
991         return com_la_info(fd, argc, argv);
992 }
993
994 static int change_stream(const char *stream)
995 {
996         char *query;
997         int ret;
998         query = make_message("update streams set def='%s' "
999                 "where name = 'current_stream'", stream);
1000         ret = real_query(query);
1001         free(query);
1002         return ret;
1003 }
1004
1005 static int get_pic_id_by_name(char *name)
1006 {
1007         char *q, *ebn;
1008         void *result = NULL;
1009         int ret;
1010         MYSQL_ROW row;
1011
1012         if (!(ebn = escaped_basename(name)))
1013                 return -E_ESCAPE;
1014         q = make_message("select pic_id from data where name = '%s'", ebn);
1015         free(ebn);
1016         result = get_result(q);
1017         free(q);
1018         if (!result)
1019                 return -E_NORESULT;
1020         row = mysql_fetch_row(result);
1021         ret = -E_NOROW;
1022         if (row && row[0])
1023                 ret = atoi(row[0]);
1024         mysql_free_result(result);
1025         return ret;
1026 }
1027
1028 static int remove_entry(const char *name)
1029 {
1030         char *q, *ebn = escaped_basename(name);
1031         int ret = -E_ESCAPE;
1032
1033         if (!ebn)
1034                 goto out;
1035         q = make_message("delete from data where name = '%s'", ebn);
1036         real_query(q); /* ignore errors */
1037         free(q);
1038         q = make_message("delete from dir where name = '%s'", ebn);
1039         real_query(q); /* ignore errors */
1040         free(q);
1041         ret = 1;
1042 out:
1043         free(ebn);
1044         return ret;
1045 }
1046
1047 static int add_entry(const char *name)
1048 {
1049         char *q, *dn, *ebn = NULL, *edn = NULL;
1050         int ret;
1051
1052         if (!name || !*name)
1053                 return -E_MYSQL_SYNTAX;
1054         ebn = escaped_basename(name);
1055         if (!ebn)
1056                 return -E_ESCAPE;
1057         ret = -E_MYSQL_SYNTAX;
1058         dn = para_dirname(name);
1059         if (!dn)
1060                 goto out;
1061         ret = -E_ESCAPE;
1062         edn = escape_str(dn);
1063         free(dn);
1064         if (!edn || !*edn)
1065                 goto out;
1066         q = make_message("insert into data (name, pic_id) values "
1067                         "('%s', '%s')", ebn, "1");
1068         ret = real_query(q);
1069 //      ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
1070         free(q);
1071         if (ret < 0)
1072                 goto out;
1073         q = make_message("insert into dir (name, dir) values "
1074                         "('%s', '%s')", ebn, edn);
1075 //      ret = 1; PARA_DEBUG_LOG("q: %s\n", q);
1076         ret = real_query(q);
1077         free(q);
1078 out:
1079         if (ebn)
1080                 free(ebn);
1081         if (edn)
1082                 free(edn);
1083         return ret;
1084 }
1085
1086 /*
1087  * remove/add entries
1088  */
1089 static int com_rm_ne(__a_unused int fd, int argc, char *argv[])
1090 {
1091         int ne = !strcmp(argv[0], "ne");
1092         int i, ret;
1093         if (argc < 2)
1094                 return -E_MYSQL_SYNTAX;
1095         for (i = 1; i < argc; i++) {
1096                 ret = remove_entry(argv[i]);
1097                 if (ret < 0)
1098                         return ret;
1099                 if (!ne)
1100                         continue;
1101                 ret = add_entry(argv[i]);
1102                 if (ret < 0)
1103                         return ret;
1104         }
1105         return 1;
1106 }
1107
1108 /*
1109  * rm
1110  */
1111 int com_rm(int fd, int argc, char *argv[])
1112 {
1113         return com_rm_ne(fd, argc, argv);
1114 }
1115
1116 /*
1117  * ne
1118  */
1119 int com_ne(int fd, int argc, char *argv[])
1120 {
1121         return com_ne(fd, argc, argv);
1122 }
1123
1124 /*
1125  * mv: rename entry
1126  */
1127 int com_mv(__a_unused int fd, int argc, char *argv[])
1128 {
1129         char *q, *dn, *ebn1 = NULL, *ebn2 = NULL, *edn = NULL;
1130         int ret;
1131
1132         if (argc != 3)
1133                 return -E_MYSQL_SYNTAX;
1134         ret = -E_ESCAPE;
1135         ebn1 = escaped_basename(argv[1]);
1136         ebn2 = escaped_basename(argv[2]);
1137         if (!ebn1 || !ebn2 || !*ebn1 || !*ebn2)
1138                 goto out;
1139         ret = -E_MYSQL_SYNTAX;
1140         if (!strcmp(ebn1, ebn2))
1141                 goto update_dir;
1142         remove_entry(argv[2]); /* no need to escape, ignore error */
1143         q = make_message("update data set name = '%s' where name = '%s'",
1144                 ebn2, ebn1);
1145         ret = real_query(q);
1146         free(q);
1147         if (ret < 0)
1148                 goto out;
1149         ret = -E_AUDIO_FILE;
1150         if (!mysql_affected_rows(mysql_ptr))
1151                 goto out;
1152         q = make_message("update dir set name = '%s' where name = '%s'",
1153                 ebn2, ebn1);
1154         ret = real_query(q);
1155         free(q);
1156         if (ret < 0)
1157                 goto out;
1158 update_dir:
1159         ret = 1;
1160         dn = para_dirname(argv[2]);
1161         if (!dn)
1162                 goto out;
1163         ret = -E_ESCAPE;
1164         edn = escape_str(dn);
1165         free(dn);
1166         if (!edn)
1167                 goto out;
1168         ret = 1;
1169         if (!*edn)
1170                 goto out;
1171         q = make_message("update dir set dir = '%s' where name = '%s'",
1172                 edn, ebn2);
1173         ret = real_query(q);
1174         free(q);
1175 out:
1176         free(edn);
1177         free(ebn1);
1178         free(ebn2);
1179         return ret;
1180 }
1181
1182 /*
1183  * set field
1184  */
1185 static int com_set(__a_unused int fd, int argc, char *argv[])
1186 {
1187         char *q, *ebn;
1188         long unsigned id;
1189         int i, ret;
1190         const char *field = strcmp(argv[0], "picass")? "numplayed" : "pic_id";
1191
1192         if (argc < 3)
1193                 return -E_MYSQL_SYNTAX;
1194         id = atol(argv[1]);
1195         for (i = 2; i < argc; i++) {
1196                 ebn = escaped_basename(argv[i]);
1197                 if (!ebn)
1198                         return -E_ESCAPE;
1199                 q = make_message("update data set %s = %lu "
1200                                 "where name = '%s'", field, id, ebn);
1201                 free(ebn);
1202                 ret = real_query(q);
1203                 free(q);
1204                 if (ret < 0)
1205                         return ret;
1206         }
1207         return 1;
1208 }
1209
1210 /*
1211  * snp: set numplayed
1212  */
1213 int com_picass(int fd, int argc, char *argv[])
1214 {
1215         return com_set(fd, argc, argv);
1216 }
1217
1218 /*
1219  * snp: set numplayed
1220  */
1221 int com_snp(int fd, int argc, char *argv[])
1222 {
1223         int ret = com_set(fd, argc, argv);
1224         if (ret >= 0)
1225                 refresh_selector_info();
1226         return ret;
1227 }
1228
1229 /*
1230  * picch: change entry's name in pics table
1231  */
1232 int com_picch(__a_unused int fd, int argc, char *argv[])
1233 {
1234         int ret;
1235         long unsigned id;
1236         char *q, *tmp;
1237
1238         if (argc != 3)
1239                 return -E_MYSQL_SYNTAX;
1240         id = atol(argv[1]);
1241         ret = -E_ESCAPE;
1242         tmp = escape_str(argv[2]);
1243         if (!tmp)
1244                 return -E_ESCAPE;
1245         q = make_message("update pics set name = '%s' where id = %lu", tmp, id);
1246         free(tmp);
1247         ret = real_query(q);
1248         free(q);
1249         return ret;
1250 }
1251
1252 /*
1253  * piclist: print list of pics in db
1254  */
1255 int com_piclist(__a_unused int fd, int argc, __a_unused char *argv[])
1256 {
1257         void *result = NULL;
1258         MYSQL_ROW row;
1259         unsigned long *length;
1260         int ret;
1261
1262         if (argc != 1)
1263                 return -E_MYSQL_SYNTAX;
1264         result = get_result("select id,name,pic from pics order by id");
1265         if (!result)
1266                 return -E_NORESULT;
1267         while ((row = mysql_fetch_row(result))) {
1268                 length = mysql_fetch_lengths(result);
1269                 if (!row || !row[0] || !row[1] || !row[2])
1270                         continue;
1271                 ret = send_va_buffer(fd, "%s\t%lu\t%s\n", row[0], length[2], row[1]);
1272                 if (ret < 0)
1273                         goto out;
1274         }
1275         ret = 1;
1276 out:
1277         mysql_free_result(result);
1278         return ret;
1279 }
1280
1281 /*
1282  * picdel: delete picture from database
1283  */
1284 int com_picdel(int fd, int argc, char *argv[])
1285 {
1286         char *q;
1287         long unsigned id;
1288         my_ulonglong aff;
1289         int i, ret;
1290
1291         if (argc < 2)
1292                 return -E_MYSQL_SYNTAX;
1293         for (i = 1; i < argc; i++) {
1294                 id = atol(argv[i]);
1295                 q = make_message("delete from pics where id = %lu", id);
1296                 ret = real_query(q);
1297                 free(q);
1298                 if (ret < 0)
1299                         return ret;
1300                 aff = mysql_affected_rows(mysql_ptr);
1301                 if (!aff) {
1302                         ret = send_va_buffer(fd, "No such id: %lu\n", id);
1303                         if (ret < 0)
1304                                 return ret;
1305                         continue;
1306                 }
1307                 q = make_message("update data set pic_id = 1 where pic_id = %lu", id);
1308                 ret = real_query(q);
1309                 free(q);
1310         }
1311         return 1;
1312 }
1313 /*
1314  * pic: get picture by name or by number
1315  */
1316 int com_pic(int fd, int argc, char *argv[])
1317 {
1318         void *result = NULL;
1319         MYSQL_ROW row;
1320         unsigned long *length, id;
1321         int ret;
1322         char *q, *name = NULL;
1323
1324         if (argc < 2) {
1325                 ret = -E_GET_AUDIO_FILE;
1326                 name = get_current_audio_file();
1327         } else {
1328                 ret = -E_ESCAPE;
1329                 name = escaped_basename(argv[1]);
1330         }
1331         if (!name)
1332                 return ret;
1333         if (*name == '#')
1334                 id = atoi(name + 1);
1335         else
1336                 id = get_pic_id_by_name(name);
1337         free(name);
1338         if (id <= 0)
1339                 return id;
1340         q = make_message("select pic from pics where id = '%lu'", id);
1341         result = get_result(q);
1342         free(q);
1343         if (!result)
1344                 return -E_NORESULT;
1345         row = mysql_fetch_row(result);
1346         ret = -E_NOROW;
1347         if (!row || !row[0])
1348                 goto out;
1349         length = mysql_fetch_lengths(result);
1350         ret = send_bin_buffer(fd, row[0], *length);
1351 out:
1352         mysql_free_result(result);
1353         return ret;
1354 }
1355
1356 /* strdel */
1357 int com_strdel(__a_unused int fd, int argc, char *argv[])
1358 {
1359         char *q, *tmp;
1360         int ret;
1361
1362         if (argc < 2)
1363                 return -E_MYSQL_SYNTAX;
1364         tmp = escape_str(argv[1]);
1365         if (!tmp)
1366                 return -E_ESCAPE;
1367         q = make_message("delete from streams where name='%s'", tmp);
1368         free(tmp);
1369         ret = real_query(q);
1370         free(q);
1371         return ret;
1372 }
1373
1374 /*
1375  * ls
1376  */
1377 int com_ls(int fd, int argc, char *argv[])
1378 {
1379         char *q;
1380         void *result;
1381         int ret;
1382         my_ulonglong num_rows, top = 0, left = 0, right = 0;
1383
1384         if (argc > 2)
1385                 return -E_MYSQL_SYNTAX;
1386         if (argc > 1) {
1387                 char *tmp = escape_str(argv[1]);
1388                 if (!tmp)
1389                         return -E_ESCAPE;
1390                 q = make_message("select name from data where name like '%s'",
1391                         tmp);
1392                 free(tmp);
1393         } else
1394                 q = para_strdup("select name from data");
1395         result = get_result(q);
1396         free(q);
1397         if (!result)
1398                 return -E_NORESULT;
1399         num_rows = mysql_num_rows(result);
1400         ret = 1;
1401         if (num_rows)
1402                 ret = print_results(fd, result, top, left, num_rows - 1, right);
1403         mysql_free_result(result);
1404         return ret;
1405 }
1406
1407 /*
1408  * summary
1409  */
1410 int com_summary(__a_unused int fd, int argc, __a_unused char *argv[])
1411 {
1412         MYSQL_ROW row;
1413         MYSQL_ROW row2;
1414         void *result;
1415         void *result2 = NULL;
1416         const char *fmt = "select count(name) from data where %s='1'";
1417         int ret = -E_NORESULT;
1418
1419         if (argc != 1)
1420                 return -E_MYSQL_SYNTAX;
1421         result = get_all_attributes();
1422         if (!result)
1423                 goto out;
1424         while ((row = mysql_fetch_row(result))) {
1425                 char *buf;
1426
1427                 ret = -E_NOROW;
1428                 if (!row[0])
1429                         goto out;
1430                 ret = -E_NORESULT;
1431                 buf = make_message(fmt, row[0]);
1432                 result2 = get_result(buf);
1433                 free(buf);
1434                 if (!result2)
1435                         goto out;
1436                 ret = -E_NOROW;
1437                 row2 = mysql_fetch_row(result2);
1438                 if (!row2 || !row2[0])
1439                         goto out;
1440                 ret = send_va_buffer(fd, "%s\t%s\n", row[0], row2[0]);
1441                 if (ret < 0)
1442                         goto out;
1443         }
1444         ret = 1;
1445 out:
1446         if (result2)
1447                 mysql_free_result(result2);
1448         if (result)
1449                 mysql_free_result(result);
1450         return ret;
1451 }
1452
1453 static int get_numplayed(char *name)
1454 {
1455         void *result;
1456         MYSQL_ROW row;
1457         const char *fmt = "select numplayed from data where name = '%s'";
1458         char *buf = make_message(fmt, name);
1459         int ret = -E_NORESULT;
1460
1461         result = get_result(buf);
1462         free(buf);
1463         if (!result)
1464                 goto out;
1465         ret = -E_NOROW;
1466         row = mysql_fetch_row(result);
1467         if (!row || !row[0])
1468                 goto out;
1469         ret = atoi(row[0]);
1470 out:
1471         if (result)
1472                 mysql_free_result(result);
1473         return ret;
1474 }
1475
1476 static int update_audio_file(char *name)
1477 {
1478         int ret;
1479         const char *fmt1 = "update data set lastplayed = now() where name = '%s'";
1480         const char *fmt2 = "update data set numplayed = %i where name = '%s'";
1481         char *q;
1482         char *ebn = escaped_basename(name);
1483
1484         ret = -E_ESCAPE;
1485         if (!ebn)
1486                 goto out;
1487         q = make_message(fmt1, ebn);
1488         ret = real_query(q);
1489         free(q);
1490         if (ret < 0)
1491                 goto out;
1492         ret = get_numplayed(ebn);
1493         if (ret < 0)
1494                 goto out;
1495         q = make_message(fmt2, ret + 1, ebn);
1496         ret = real_query(q);
1497         free(q);
1498 out:
1499         free(ebn);
1500         return ret;
1501 }
1502
1503 static void update_audio_file_server_handler(char *name)
1504 {
1505         char *info;
1506         info = get_selector_info(name);
1507         update_mmd(info);
1508         free(info);
1509         update_audio_file(name);
1510 }
1511
1512 int com_us(__a_unused int fd, int argc, char *argv[])
1513 {
1514         char *tmp;
1515         int ret;
1516
1517         if (argc != 2)
1518                 return -E_MYSQL_SYNTAX;
1519         tmp = escape_str(argv[1]);
1520         if (!tmp)
1521                 return -E_ESCAPE;
1522         ret = update_audio_file(argv[1]);
1523         free(tmp);
1524         if (ret >= 0)
1525                 refresh_selector_info();
1526         return ret;
1527 }
1528
1529 /* select previous / next stream */
1530 static int com_ps_ns(__a_unused int fd, int argc, char *argv[])
1531 {
1532         char *query, *stream = get_current_stream();
1533         void *result = get_result("select name from streams");
1534         MYSQL_ROW row;
1535         int ret;
1536         my_ulonglong num_rows, match, i;
1537
1538         ret = -E_MYSQL_SYNTAX;
1539         if (argc != 1)
1540                 goto out;
1541         ret = -E_NORESULT;
1542         if (!result)
1543                 goto out;
1544         num_rows = mysql_num_rows(result);
1545         ret = -E_EMPTY_RESULT;
1546         if (num_rows < 2)
1547                 goto out;
1548         ret = -E_NOROW;
1549         for (i = 0; i < num_rows; i++) {
1550                 row = mysql_fetch_row(result);
1551                 if (!row || !row[0])
1552                         goto out;
1553                 if (!strcmp(row[0], "current_stream"))
1554                         continue;
1555                 if (!strcmp(row[0], stream))
1556                         break;
1557         }
1558         ret = -E_NO_STREAM;
1559         if (i == num_rows)
1560                 goto out;
1561         match = i;
1562         if (!strcmp(argv[0], "ps"))
1563                 i = match > 0? match - 1 : num_rows - 1;
1564         else
1565                 i = match < num_rows - 1? match + 1 : 0;
1566         ret = -E_NOROW;
1567         mysql_data_seek(result, i);
1568         row = mysql_fetch_row(result);
1569         if (!row || !row[0])
1570                 goto out;
1571         if (!strcmp(row[0], "current_stream")) {
1572                 if (!strcmp(argv[0], "ps")) {
1573                         i = match < 2? match + num_rows - 2 : match - 2;
1574                 } else {
1575                         i = match + 2;
1576                         i = i > num_rows - 1? i - num_rows : i;
1577                 }
1578                 mysql_data_seek(result, i);
1579                 row = mysql_fetch_row(result);
1580                 if (!row || !row[0])
1581                         goto out;
1582         }
1583         query = make_message("update streams set def='%s' where name = "
1584                 "'current_stream'", row[0]);
1585         ret = real_query(query);
1586         free(query);
1587         refresh_selector_info();
1588 out:
1589         free(stream);
1590         if (result)
1591                 mysql_free_result(result);
1592         return ret;
1593 }
1594
1595 /* select previous stream */
1596 int com_ps(int fd, int argc, char *argv[])
1597 {
1598         return com_ps_ns(fd, argc, argv);
1599 }
1600
1601 /* select next stream */
1602 int com_ns(int fd, int argc, char *argv[])
1603 {
1604         return com_ps_ns(fd, argc, argv);
1605 }
1606
1607 /* streams */
1608 int com_streams(int fd, int argc, __a_unused char *argv[])
1609 {
1610         unsigned int num_rows;
1611         int i, ret = -E_NORESULT;
1612         void *result;
1613         MYSQL_ROW row;
1614
1615         if (argc > 1 && strcmp(argv[1], "current_stream"))
1616                 return -E_MYSQL_SYNTAX;
1617         if (argc > 1) {
1618                 char *cs = get_current_stream();
1619                 ret = send_va_buffer(fd, "%s\n", cs);
1620                 free(cs);
1621                 return ret;
1622         }
1623         result = get_result("select name from streams");
1624         if (!result)
1625                 goto out;
1626         num_rows = mysql_num_rows(result);
1627         ret = 1;
1628         if (!num_rows)
1629                 goto out;
1630         ret = -E_NOROW;
1631         for (i = 0; i < num_rows; i++) {
1632                 row = mysql_fetch_row(result);
1633                 if (!row || !row[0])
1634                         goto out;
1635                 if (strcmp(row[0], "current_stream"))
1636                         send_va_buffer(fd, "%s\n", row[0]);
1637         }
1638         ret = 1;
1639 out:
1640         if (result)
1641                 mysql_free_result(result);
1642         return ret;
1643 }
1644
1645 /* query stream definition */
1646 int com_strq(int fd, int argc, char *argv[])
1647 {
1648         MYSQL_ROW row;
1649         char *query, *name;
1650         void *result;
1651         int ret;
1652
1653         if (argc < 2) {
1654                 ret = -E_GET_STREAM;
1655                 name = get_current_stream();
1656         } else {
1657                 ret = -E_ESCAPE;
1658                 name = escaped_basename(argv[1]);
1659         }
1660         if (!name)
1661                 return ret;
1662         ret = -E_NORESULT;
1663         query = make_message("select def from streams where name='%s'", name);
1664         free(name);
1665         result = get_result(query);
1666         free(query);
1667         if (!result)
1668                 goto out;
1669         ret = -E_NOROW;
1670         row = mysql_fetch_row(result);
1671         if (!row || !row[0])
1672                 goto out;
1673         /* no '\n' needed */
1674         ret = send_buffer(fd, row[0]);
1675 out:
1676         if (result)
1677                 mysql_free_result(result);
1678         return ret;
1679 }
1680
1681 /* change stream / change stream and play */
1682 static int com_cs_csp(int fd, int argc, char *argv[])
1683 {
1684         int ret, stream_change;
1685         char *query, *stream = NULL;
1686         char *old_stream = get_current_stream();
1687         int csp = !strcmp(argv[0], "csp");
1688
1689         ret = -E_MYSQL_SYNTAX;
1690         if (argc > 2)
1691                 goto out;
1692         if (argc == 1) {
1693                 if (csp)
1694                         goto out;
1695                 ret = send_va_buffer(fd, "%s\n", old_stream);
1696                 goto out;
1697         }
1698         ret = -E_GET_QUERY;
1699         /* test if stream is valid, no need to escape argv[1] */
1700         query = get_query(argv[1], NULL, 0);
1701         if (!query)
1702                 goto out;
1703         free(query);
1704         /* stream is ok */
1705         stream = escape_str(argv[1]);
1706         if (!stream)
1707                 goto out;
1708         stream_change = strcmp(stream, old_stream);
1709         if (stream_change) {
1710                 ret = change_stream(stream);
1711                 if (ret < 0)
1712                         goto out;
1713                 refresh_selector_info();
1714         }
1715         if (csp) {
1716                 mmd_lock();
1717                 mmd->new_vss_status_flags |= VSS_PLAYING;
1718                 if (stream_change)
1719                         mmd->new_vss_status_flags |= VSS_NEXT;
1720                 mmd_unlock();
1721         }
1722         ret = 1;
1723 out:
1724         free(old_stream);
1725         free(stream);
1726         return ret;
1727 }
1728
1729 /* change stream */
1730 int com_cs(int fd, int argc, char *argv[])
1731 {
1732         return com_cs_csp(fd, argc, argv);
1733 }
1734
1735 /* change stream and play */
1736 int com_csp(int fd, int argc, char *argv[])
1737 {
1738         return com_cs_csp(fd, argc, argv);
1739 }
1740
1741 /* score list / skip */
1742 static int com_sl_skip(int fd, int argc, char *argv[])
1743 {
1744         void *result = NULL;
1745         MYSQL_ROW row;
1746         int ret, i, skip = !strcmp(argv[0], "skip");
1747         char *query, *stream, *tmp;
1748         unsigned int num_rows, num;
1749
1750         if (argc < 2)
1751                 return -E_MYSQL_SYNTAX;
1752         num = atoi(argv[1]);
1753         if (!num)
1754                 return -E_MYSQL_SYNTAX;
1755         if (argc == 2) {
1756                 stream = get_current_stream();
1757                 if (!stream)
1758                         return -E_GET_STREAM;
1759         } else {
1760                 stream = escape_str(argv[2]);
1761                 if (!stream)
1762                         return -E_ESCAPE;
1763         }
1764         tmp = get_query(stream, NULL, 0);
1765         free(stream);
1766         if (!tmp)
1767                 return -E_GET_QUERY;
1768         query = make_message("%s limit %d", tmp, num);
1769         free(tmp);
1770         ret = -E_NORESULT;
1771         result = get_result(query);
1772         free(query);
1773         if (!result)
1774                 goto out;
1775         ret = -E_EMPTY_RESULT;
1776         num_rows = mysql_num_rows(result);
1777         if (!num_rows)
1778                 goto out;
1779         for (i = 0; i < num_rows && i < num; i++) {
1780                 row = mysql_fetch_row(result);
1781                 if (skip) {
1782                         send_va_buffer(fd, "Skipping %s\n", row[0]);
1783                         update_audio_file(row[0]);
1784                 } else
1785                         send_va_buffer(fd, "%s\n", row[0]? row[0]: "BUG");
1786         }
1787         ret = 1;
1788 out:
1789         if (result)
1790                 mysql_free_result(result);
1791         return ret;
1792 }
1793
1794 /* score list */
1795 int com_sl(int fd, int argc, char *argv[])
1796 {
1797         return com_sl_skip(fd, argc, argv);
1798 }
1799
1800 /* skip */
1801 int com_skip(int fd, int argc, char *argv[])
1802 {
1803         return com_sl_skip(fd, argc, argv);
1804 }
1805
1806 /*
1807  * update attributes of name
1808  */
1809 static int update_atts(int fd, char *name, char *atts)
1810 {
1811         int ret;
1812         char *ebn, *q, *old, *new = NULL;
1813
1814         if (!mysql_ptr)
1815                 return -E_NOTCONN;
1816         ebn = escaped_basename(name);
1817         if (!ebn)
1818                 return -E_ESCAPE;
1819         q = make_message("update data set %s where name = '%s'", atts, ebn);
1820         old = get_atts(ebn, 0);
1821         send_va_buffer(fd, "old: %s\n", old);
1822         free(old);
1823         ret = real_query(q);
1824         free(q);
1825         if (ret < 0)
1826                 goto out;
1827         new = get_atts(ebn, 0);
1828         ret = send_va_buffer(fd, "new: %s\n", new);
1829         free(new);
1830 out:
1831         free(ebn);
1832         return ret;
1833 }
1834
1835 /*
1836  * set attributes
1837  */
1838 int com_sa(int fd, int argc, char *argv[])
1839 {
1840         int i, ret;
1841         char *atts = NULL, *name;
1842
1843         if (argc < 2)
1844                 return -E_MYSQL_SYNTAX;
1845         for (i = 1; i < argc; i++) {
1846                 int unset = 0;
1847                 char *esc, *tmp, *p =argv[i];
1848                 int len = strlen(p);
1849
1850                 if (!len)
1851                         continue;
1852                 switch (p[len - 1]) {
1853                         case '+':
1854                                 unset = 0;
1855                                 break;
1856                         case '-':
1857                                 unset = 1;
1858                                 break;
1859                         default:
1860                                 goto no_more_atts;
1861                 }
1862                 p[len - 1] = '\0';
1863                 esc = escape_str(p);
1864                 if (!esc)
1865                         return -E_ESCAPE;
1866                 tmp = make_message("%s%s='%s'", atts? "," : "", esc,
1867                         unset? "0" : "1");
1868                 free(esc);
1869                 atts = para_strcat(atts, tmp);
1870                 free(tmp);
1871         }
1872 no_more_atts:
1873         if (!atts)
1874                 return -E_NOATTS;
1875         if (i >= argc) { /* no name given, use current af */
1876                 ret = -E_GET_AUDIO_FILE;
1877                 if (!(name = get_current_audio_file()))
1878                         goto out;
1879                 ret = update_atts(fd, name, atts);
1880                 free(name);
1881         } else {
1882                 ret = 1;
1883                 for (; argv[i] && ret >= 0; i++)
1884                         ret = update_atts(fd, argv[i], atts);
1885         }
1886         refresh_selector_info();
1887 out:
1888         free(atts);
1889         return ret;
1890 }
1891
1892 /*
1893  * copy attributes
1894  */
1895 int com_cam(int fd, int argc, char *argv[])
1896 {
1897         char *name = NULL, *meta = NULL, *atts = NULL;
1898         int i, ret;
1899
1900         if (argc < 3)
1901                 return -E_MYSQL_SYNTAX;
1902         if (!(name = escaped_basename(argv[1])))
1903                 return -E_ESCAPE;
1904         ret = -E_NOATTS;
1905         if (!(atts = get_atts(name, 1)))
1906                 goto out;
1907         ret = -E_META;
1908         if (!(meta = get_meta(name, 0)))
1909                 goto out;
1910         for (i = 2; i < argc; i++) {
1911                 char *ebn, *q;
1912                 ret = -E_ESCAPE;
1913                 if (!(ebn = escaped_basename(argv[i])))
1914                         goto out;
1915                 ret = send_va_buffer(fd, "updating %s\n", ebn);
1916                 if (ret < 0) {
1917                         free(ebn);
1918                         goto out;
1919                 }
1920                 q = make_message("update data set %s where name = '%s'",
1921                         meta, ebn);
1922                 if ((ret = update_atts(fd, ebn, atts)) >= 0)
1923                         ret = real_query(q);
1924                 free(ebn);
1925                 free(q);
1926                 if (ret < 0)
1927                         goto out;
1928         }
1929         ret = 1;
1930 out:
1931         if (name)
1932                 free(name);
1933         if (meta)
1934                 free(meta);
1935         if (atts)
1936                 free(atts);
1937         return ret;
1938 }
1939
1940 /*
1941  * verify / clean
1942  */
1943 static int com_vrfy_clean(int fd, int argc, __a_unused char *argv[])
1944 {
1945         char *query;
1946         int ret, vrfy_mode = strcmp(argv[0], "clean");
1947         void *result = NULL;
1948         MYSQL_ROW row;
1949         char *escaped_name;
1950         my_ulonglong num_rows, top = 0, left = 0, right = 0;
1951
1952         if (argc != 1)
1953                 return -E_MYSQL_SYNTAX;
1954         ret = -E_NORESULT;
1955         result = get_result("select data.name from data left join dir on "
1956                 "dir.name = data.name where dir.name is NULL");
1957         if (!result)
1958                 goto out;
1959         num_rows = mysql_num_rows(result);
1960         if (!num_rows) {
1961                 ret = send_buffer(fd, "No invalid entries\n");
1962                 goto out;
1963         }
1964         if (vrfy_mode) {
1965                 send_va_buffer(fd, "found %lli invalid entr%s\n", num_rows,
1966                         num_rows == 1? "y" : "ies");
1967                 ret = print_results(fd, result, top, left, num_rows - 1, right);
1968                 goto out;
1969         }
1970         while ((row = mysql_fetch_row(result))) {
1971                 ret = -E_NOROW;
1972                 if (!row[0])
1973                         goto out;
1974                 ret = -E_ESCAPE;
1975                 escaped_name = escape_str(row[0]);
1976                 if (!escaped_name)
1977                         goto out;
1978                 send_va_buffer(fd, "deleting %s\n", escaped_name);
1979                 query = make_message("delete from data where name = '%s'",
1980                         escaped_name);
1981                 ret = real_query(query);
1982                 free(query);
1983                 if (ret < 0)
1984                         goto out;
1985         }
1986
1987 out:
1988         if (result)
1989                 mysql_free_result(result);
1990         return ret;
1991 }
1992
1993 /*
1994  * verify
1995  */
1996 int com_vrfy(int fd, int argc, char **argv)
1997 {
1998         return com_vrfy_clean(fd, argc, argv);
1999 }
2000
2001 /*
2002  * clean
2003  */
2004 int com_clean(int fd, int argc, char **argv)
2005 {
2006         return com_vrfy_clean(fd, argc, argv);
2007 }
2008
2009 static FILE *out_file;
2010
2011 static int mysql_write_tmp_file(const char *dir, const char *name)
2012 {
2013         int ret = -E_TMPFILE;
2014         char *msg = make_message("%s\t%s\n", dir, name);
2015         if (fputs(msg, out_file) != EOF)
2016                 ret = 1;
2017         free(msg);
2018         return ret;
2019 }
2020
2021 /*
2022  * update database
2023  */
2024 int com_upd(int fd, int argc, __a_unused char *argv[])
2025 {
2026         char *tempname = NULL, *query = NULL;
2027         int ret, out_fd = -1, num = 0;
2028         void *result = NULL;
2029         unsigned int num_rows;
2030         MYSQL_ROW row;
2031
2032         if (argc != 1)
2033                 return -E_MYSQL_SYNTAX;
2034         out_file = NULL;
2035         tempname = para_strdup("/tmp/mysql.tmp.XXXXXX");
2036         ret = para_mkstemp(tempname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
2037         if (ret < 0)
2038                 goto out;
2039         out_fd = ret;
2040         out_file = fdopen(out_fd, "w");
2041         if (!out_file) {
2042                 close(out_fd);
2043                 goto out;
2044         }
2045         if (find_audio_files(conf.mysql_audio_file_dir_arg, mysql_write_tmp_file) < 0)
2046                 goto out;
2047         num = ftell(out_file);
2048         /*
2049          * we have to make sure the file hit the disk before we call
2050          * real_query
2051          */
2052         fclose(out_file);
2053         out_file = NULL;
2054         PARA_DEBUG_LOG("wrote tempfile %s (%d bytes)\n", tempname, num);
2055         if (!num)
2056                 goto out;
2057         if ((ret = real_query("delete from dir")) < 0)
2058                 goto out;
2059         query = make_message("load data infile '%s' ignore into table dir "
2060                 "fields terminated by '\t' lines terminated by '\n' "
2061                 "(dir, name)", tempname);
2062         ret = real_query(query);
2063         free(query);
2064         if (ret < 0)
2065                 goto out;
2066         result = get_result("select dir.name from dir left join data on "
2067                 "data.name = dir.name where data.name is NULL");
2068         ret = -E_NORESULT;
2069         if (!result)
2070                 goto out;
2071         num_rows = mysql_num_rows(result);
2072         if (!num_rows) {
2073                 ret = send_buffer(fd, "no new entries\n");
2074                 goto out;
2075         }
2076         while ((row = mysql_fetch_row(result))) {
2077                 char *erow;
2078                 ret = -E_NOROW;
2079                 if (!row[0])
2080                         goto out;
2081                 send_va_buffer(fd, "new entry: %s\n", row[0]);
2082                 erow = escape_str(row[0]);
2083                 if (!erow)
2084                         goto out;
2085                 query = make_message("insert into data (name, pic_id) values "
2086                         "('%s','%s')", erow, "1");
2087                 free(erow);
2088                 ret = real_query(query);
2089                 free(query);
2090                 if (ret < 0)
2091                         goto out;
2092         }
2093         ret = 1;
2094 out:
2095         if (out_fd >= 0)
2096                 unlink(tempname);
2097         free(tempname);
2098         if (out_file)
2099                 fclose(out_file);
2100         if (result)
2101                 mysql_free_result(result);
2102         return ret;
2103 }
2104
2105 static char **server_get_audio_file_list(unsigned int num)
2106 {
2107         char **list = para_malloc((num + 1) * sizeof(char *));
2108         char *tmp, *query, *stream = get_current_stream();
2109         void *result = NULL;
2110         unsigned int num_rows;
2111         int i = 0;
2112         MYSQL_ROW row;
2113
2114         tmp = get_query(stream, NULL, 1);
2115         free(stream);
2116         if (!tmp)
2117                 goto err_out;
2118         query = make_message("%s limit %d", tmp, num);
2119         free(tmp);
2120         result = get_result(query);
2121         free(query);
2122         if (!result)
2123                 goto err_out;
2124         num_rows = mysql_num_rows(result);
2125         if (!num_rows)
2126                 goto err_out;
2127         for (i = 0; i < num_rows && i < num; i++) {
2128                 row = mysql_fetch_row(result);
2129                 if (!row || !row[0])
2130                         goto err_out;
2131                 list[i] = para_strdup(row[0]);
2132         }
2133         list[i] = NULL;
2134         goto success;
2135 err_out:
2136         while (i > 0) {
2137                 i--;
2138                 free(list[i]);
2139         }
2140         free(list);
2141         list = NULL;
2142 success:
2143         if (result)
2144                 mysql_free_result(result);
2145         return list;
2146 }
2147
2148 /*
2149  * connect to mysql server, return mysql pointer on success, -E_NOTCONN
2150  * on errors. Called from parent on startup and also from com_cdb().
2151  */
2152 static int init_mysql_server(void)
2153 {
2154         char *u = conf.mysql_user_arg? conf.mysql_user_arg : para_logname();
2155         unsigned int port;
2156
2157         mysql_ptr = mysql_init(NULL);
2158         if (!mysql_ptr) {
2159                 PARA_CRIT_LOG("%s", "mysql init error\n");
2160                 return -E_NOTCONN;
2161         }
2162         if (conf.mysql_port_arg < 0)
2163                 return -E_MYSQL_SYNTAX;
2164         port = conf.mysql_port_arg;
2165         PARA_DEBUG_LOG("connecting: %s@%s:%d\n", u, conf.mysql_host_arg, port);
2166         if (!conf.mysql_user_arg)
2167                 free(u);
2168         /*
2169          * If host is NULL a connection to the local host is assumed,
2170          * If user is NULL, the current user is assumed
2171          */
2172         if (!(mysql_ptr = mysql_real_connect(mysql_ptr,
2173                         conf.mysql_host_arg,
2174                         conf.mysql_user_arg,
2175                         conf.mysql_passwd_arg,
2176                         conf.mysql_database_arg,
2177                         port, NULL, 0))) {
2178                 PARA_CRIT_LOG("%s", "connect error\n");
2179                 return -E_NOTCONN;
2180         }
2181         PARA_INFO_LOG("%s", "success\n");
2182         return 1;
2183 }
2184
2185 /* mmd lock must be held */
2186 static void write_msg2mmd(int success)
2187 {
2188         sprintf(mmd->selector_info, "dbinfo1:%s\ndbinfo2:mysql-%s\ndbinfo3:\n",
2189                 success < 0? PARA_STRERROR(-success) :
2190                         "successfully connected to mysql server",
2191                 success < 0? "" : mysql_get_server_info(mysql_ptr));
2192 }
2193
2194 /* create database */
2195 int com_cdb(int fd, int argc, char *argv[])
2196 {
2197         char *query;
2198         int ret;
2199
2200         if (mysql_ptr) {
2201                 PARA_INFO_LOG("%s", "closing database\n");
2202                 mysql_close(mysql_ptr);
2203         }
2204         /* dont use any database */
2205         conf.mysql_database_arg = NULL; /* leak? */
2206         ret = -E_MYSQL_INIT;
2207         if (init_mysql_server() < 0 || !mysql_ptr)
2208                 goto out;
2209         if (argc < 2)
2210                 conf.mysql_database_arg = para_strdup("paraslash");
2211         else {
2212                 ret = -E_ESCAPE;
2213                 conf.mysql_database_arg = escape_str(argv[1]);
2214                 if (!conf.mysql_database_arg)
2215                         goto out;
2216         }
2217         query = make_message("create database %s", conf.mysql_database_arg);
2218         ret = real_query(query);
2219         free(query);
2220         if (ret < 0)
2221                 goto out;
2222         /* reconnect with database just created */
2223         mysql_close(mysql_ptr);
2224         ret = -E_MYSQL_INIT;
2225         if (init_mysql_server() < 0 || !mysql_ptr)
2226                 goto out;
2227         mmd_lock();
2228         write_msg2mmd(1);
2229         mmd_unlock();
2230         ret = -E_QFAILED;
2231         if (real_query("create table data (name varchar(255) binary not null "
2232                         "primary key, "
2233                         "lastplayed datetime not null default "
2234                                 "'1970-01-01', "
2235                         "numplayed int not null default 0, "
2236                         "pic_id bigint unsigned not null default 1)") < 0)
2237                 goto out;
2238         if (real_query("create table dir (name varchar(255) binary not null "
2239                         "primary key, dir varchar(255) default null)") < 0)
2240                 goto out;
2241         if (real_query("create table pics ("
2242                         "id bigint(20) unsigned not null primary key "
2243                         "auto_increment, "
2244                         "name varchar(255) binary not null, "
2245                         "pic mediumblob not null)") < 0)
2246                 goto out;
2247         if (real_query("create table streams ("
2248                         "name varchar(255) binary not null primary key, "
2249                         "def blob not null)") < 0)
2250                 goto out;
2251         if (real_query("insert into streams (name, def) values "
2252                         "('current_stream', '(none)')") < 0)
2253                 goto out;
2254         ret = send_va_buffer(fd, "successfully created database %s\n",
2255                 conf.mysql_database_arg);
2256 out:
2257         return ret;
2258 }
2259
2260 static void shutdown_connection(void)
2261 {
2262         int ret;
2263
2264         if (mysql_ptr) {
2265                 PARA_NOTICE_LOG("%s", "shutting down mysql connection\n");
2266                 mysql_close(mysql_ptr);
2267                 mysql_ptr = NULL;
2268         }
2269         if (mysql_lock) {
2270                 ret = mutex_destroy(mysql_lock);
2271                 if (ret < 0)
2272                         PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
2273                 mysql_lock = 0;
2274         }
2275 }
2276
2277 /**
2278  * the init function of the mysql-based audio file selector
2279  *
2280  * \param db pointer to the struct to initialize
2281  *
2282  * Check the command line options and initialize all function pointers of \a
2283  * db.  Connect to the mysql server and initialize the info string.
2284  *
2285  * \return This function returns success even if it could not connect
2286  * to the mysql server. This is because the connect is expected to fail
2287  * if there the paraslash database is not yet created. This gives the
2288  * user a chance to send the "cdb" to create the database.
2289  *
2290  * \sa struct audio_file_selector, misc_meta_data::selector_info,
2291  * random_selector.c
2292  */
2293 int mysql_selector_init(struct audio_file_selector *db)
2294 {
2295         int ret;
2296
2297         if (!conf.mysql_passwd_given)
2298                 return -E_NO_MYSQL_PASSWD;
2299         if (!conf.mysql_audio_file_dir_given)
2300                 return -E_NO_AF_DIR;
2301         db->name = "mysql";
2302         db->cmd_list = mysql_selector_cmds;
2303         db->get_audio_file_list = server_get_audio_file_list;
2304         db->update_audio_file = update_audio_file_server_handler;
2305         db->shutdown = shutdown_connection;
2306         ret = mutex_new();
2307         if (ret < 0)
2308                 return ret;
2309         mysql_lock = ret;
2310         ret = init_mysql_server();
2311         if (ret < 0)
2312                 PARA_WARNING_LOG("%s\n", PARA_STRERROR(-ret));
2313         write_msg2mmd(ret);
2314         return 1;       /* return success even if connect failed to give the
2315                          * user the chance to exec com_cdb
2316                          */
2317 }