Avoid busy loop if someone nasty removes the semaphores currently in use.
[paraslash.git] / plm_dbtool.c
1 /*
2  * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 /** \file plm_dbtool.c Playlist manager for paraslash  */
20
21 #include "server.h"
22 #include "db.h"
23 #include "error.h"
24 #include "net.h"
25 #include "string.h"
26 #include "ipc.h"
27
28 struct plm_client_data {
29         size_t size;
30 /** allocated and set by com_lpl() (child) */
31         int shm_id;
32 /** initially locked, gets unlocked by parent when it is done */
33         int mutex;
34 /** return value, set by parent */
35         int retval;
36 };
37
38 /** data specific to the plm database tool */
39 struct private_plm_data {
40 /** guards against concurrent client access */
41         int client_mutex;
42 /** guards against concurrent parent-child access */
43         int server_mutex;
44 /** pointer to the client data */
45         struct plm_client_data *client_data;
46 /** id of the shm corresponding to \a client_data */
47         int client_data_shm_id;
48 };
49
50 /** we refuse to load playlists bigger than that */
51 #define MAX_PLAYLIST_BYTES (1024 * 1024)
52
53 static unsigned playlist_len, playlist_size, current_playlist_entry;
54 static char **playlist;
55 static struct dbtool *self;
56
57 static int com_ppl(int, int, char **);
58 static int com_lpl(int, int, char **);
59 extern struct misc_meta_data *mmd;
60
61 /* array of commands that are supported by this database tool */
62 static struct server_command cmds[] = {
63 {
64 .name = "ppl",
65 .handler = com_ppl,
66 .perms = DB_READ,
67 .description = "print playlist",
68 .synopsis = "ppl",
69 .help =
70 "Print out the current playlist"
71 }, {
72 .name = "lpl",
73 .handler = com_lpl,
74 .perms = DB_WRITE,
75 .description = "load playlist",
76 .synopsis = "lpl",
77 .help =
78 "Read a new playlist from stdin. Example:\n"
79 "\tfind /audio -name '*.mp3' | para_client lpl"
80 }, {
81 .name = NULL,
82 }
83 };
84
85 static void playlist_add(char *path)
86 {
87         if (playlist_len >= playlist_size) {
88                 playlist_size = 2 * playlist_size + 1;
89                 playlist = para_realloc(playlist, playlist_size * sizeof(char *));
90         }
91         PARA_DEBUG_LOG("adding #%d/%d: %s\n", playlist_len, playlist_size, path);
92         playlist[playlist_len++] = para_strdup(path);
93 }
94
95 static int send_playlist_to_server(const char *buf, size_t size)
96 {
97         struct private_plm_data *ppd = self->private_data;
98         int ret, shm_mutex = -1, shm_id = -1;
99         void *shm = NULL;
100
101         PARA_DEBUG_LOG("new playlist (%d bytes)\n", size);
102
103         ret = mutex_new();
104         if (ret < 0)
105                 return ret;
106         shm_mutex = ret;
107
108         ret = shm_new(size);
109         if (ret < 0)
110                 goto out;
111         shm_id = ret;
112
113         ret = shm_attach(shm_id, ATTACH_RW, &shm);
114         if (ret < 0)
115                 goto out;
116         mutex_lock(shm_mutex);
117         memcpy(shm, buf, size);
118         mutex_lock(ppd->client_mutex);
119         mutex_lock(ppd->server_mutex);
120         ppd->client_data->size = size;
121         ppd->client_data->shm_id = shm_id;
122         ppd->client_data->mutex = shm_mutex;
123         kill(getppid(), SIGUSR1); /* wake up the server */
124         mutex_unlock(ppd->server_mutex);
125         mutex_lock(shm_mutex); /* wait until server is done */
126         mutex_unlock(shm_mutex);
127         ret = ppd->client_data->retval;
128         mutex_unlock(ppd->client_mutex);
129         shm_detach(shm);
130 out:
131         if (shm_id >= 0)
132                 shm_destroy(shm_id);
133         mutex_destroy(shm_mutex);
134         PARA_DEBUG_LOG("returning %d\n", ret);
135         return ret;
136 }
137
138 static int com_lpl(int fd, __unused int argc, __unused char *argv[])
139 {
140         unsigned loaded = 0;
141         size_t bufsize = 4096; /* guess that's enough */
142         char *buf = para_malloc(bufsize);
143         ssize_t ret;
144         ret = send_buffer(fd, AWAITING_DATA_MSG);
145         if (ret < 0)
146                 goto out;
147 again:
148         ret = recv_bin_buffer(fd, buf + loaded, bufsize - loaded);
149         if (ret < 0)
150                 goto out;
151         if (!ret) {
152                 ret = send_playlist_to_server(buf, loaded);
153                 goto out;
154         }
155         loaded += ret;
156         ret = -E_LOAD_PLAYLIST;
157         if (loaded >= MAX_PLAYLIST_BYTES)
158                 goto out;
159         if (loaded >= bufsize) {
160                 bufsize *= 2;
161                 buf = para_realloc(buf, bufsize);
162         }
163         goto again;
164 out:
165         free(buf);
166         return ret;
167 }
168
169 static int com_ppl(int fd, __unused int argc, __unused char *argv[])
170 {
171         unsigned i;
172
173         PARA_DEBUG_LOG("sending playlist to client (%d entries)\n", playlist_len);
174         for (i = 0; i < playlist_len; i++) {
175                 int ret = send_va_buffer(fd, "%s\n", playlist[
176                         (i + current_playlist_entry) % playlist_len]);
177                 if (ret < 0)
178                         return ret;
179         }
180         return 1;
181 }
182
183 static char **plm_get_audio_file_list(unsigned int num)
184 {
185         char **file_list;
186         unsigned i;
187
188         num = MIN(num, playlist_len);
189         if (!num)
190                 return NULL;
191         file_list = para_malloc((num + 1) * sizeof(char *));
192         for (i = 0; i < num; i++) {
193                 unsigned j = (current_playlist_entry + i) % playlist_len;
194                 file_list[i] = para_strdup(playlist[j]);
195         }
196         file_list[i] = NULL;
197         return file_list;
198 }
199
200 static void free_playlist_contents(void)
201 {
202         int i;
203
204         PARA_DEBUG_LOG("freeing playlist (%d entries)\n", playlist_len);
205         for (i = 0; i < playlist_len; i++)
206                 free(playlist[i]);
207         current_playlist_entry = 0;
208         playlist_len = 0;
209 }
210
211 static void plm_shutdown(void)
212 {
213         struct private_plm_data *ppd = self->private_data;
214
215         shm_detach(ppd->client_data);
216         shm_destroy(ppd->client_data_shm_id);
217         mutex_destroy(ppd->server_mutex);
218         mutex_destroy(ppd->client_mutex);
219         free(ppd);
220         free_playlist_contents();
221         free(playlist);
222         playlist = NULL;
223         playlist_len = 0;
224         playlist_size = 0;
225 }
226
227 static void plm_post_select(__unused fd_set *rfds, __unused fd_set *wfds)
228 {
229         struct private_plm_data *ppd = self->private_data;
230         struct plm_client_data *pcd = ppd->client_data;
231         int ret;
232         void *shm;
233
234         mutex_lock(ppd->server_mutex);
235         if (!pcd->size)
236                 goto out;
237         free_playlist_contents();
238         ret = shm_attach(pcd->shm_id, ATTACH_RW, &shm);
239         if (ret < 0) {
240                 PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
241                 goto out;
242         }
243         PARA_DEBUG_LOG("loading new playlist (%d bytes)\n", pcd->size);
244         ret = for_each_line((char *)shm, pcd->size, &playlist_add, 0);
245         shm_detach(shm);
246         PARA_NOTICE_LOG("new playlist (%d entries)\n", playlist_len);
247         pcd->retval = 1;
248         pcd->size = 0;
249         mutex_unlock(pcd->mutex);
250 out:
251         mutex_unlock(ppd->server_mutex);
252 }
253
254 void plm_update_audio_file(char *audio_file)
255 {
256         unsigned i;
257
258         for (i = 0; i < playlist_len; i++) {
259                 unsigned j = (current_playlist_entry + i) % playlist_len;
260                 if (strcmp(playlist[j], audio_file))
261                         continue;
262                 current_playlist_entry = (j + 1) % playlist_len;
263         }
264 }
265
266 /**
267  * the init function for the plm database tool
268  *
269  * Init all function pointers of \a db
270  *
271  * \sa struct dbtool, misc_meta_data::dbinfo, mysql.c random_dbtool.c
272  */
273 int plm_dbtool_init(struct dbtool *db)
274 {
275         int ret;
276         struct private_plm_data *ppd = NULL;
277         void *shm = NULL;
278
279         self = db;
280         db->cmd_list = cmds;
281         db->get_audio_file_list = plm_get_audio_file_list;
282         db->shutdown = plm_shutdown;
283         db->post_select = plm_post_select;
284         db->update_audio_file = plm_update_audio_file;
285         ppd = para_calloc(sizeof(struct private_plm_data));
286         db->private_data = ppd;
287
288         ppd->client_mutex = -1;
289         ppd->server_mutex = -1;
290         ppd->client_data_shm_id = -1;
291         ppd->client_data = NULL;
292
293         ret = mutex_new();
294         if (ret < 0)
295                 goto err_out;
296         ppd->client_mutex = ret;
297
298         ret = mutex_new();
299         if (ret < 0)
300                 goto err_out;
301         ppd->server_mutex = ret;
302
303         ret = shm_new(sizeof(struct plm_client_data));
304         if (ret < 0)
305                 goto err_out;
306         ppd->client_data_shm_id = ret;
307
308         ret = shm_attach(ppd->client_data_shm_id, ATTACH_RW, &shm);
309         if (ret < 0)
310                 goto err_out;
311         ppd->client_data = shm;
312         ppd->client_data->size = 0;
313         sprintf(mmd->dbinfo, "plm initialized");
314         return 1;
315 err_out:
316         if (ppd->client_data_shm_id >= 0)
317                 shm_destroy(ppd->client_data_shm_id);
318         if (ppd->client_mutex >= 0)
319                 mutex_destroy(ppd->client_mutex);
320         if (ppd->server_mutex >= 0)
321                 mutex_destroy(ppd->server_mutex);
322         free(ppd);
323         return ret;
324 }