A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include "system.h"
23#include <errno.h>
24#include "debug.h"
25#include "file.h"
26#include "dir.h"
27#include "disk_cache.h"
28#include "fileobj_mgr.h"
29#include "rb_namespace.h"
30
31/**
32 * Manages file and directory streams on all volumes
33 *
34 * Intended for internal use by disk, file and directory code
35 */
36
37
38/* there will always be enough of these for all user handles, thus most of
39 these functions don't return failure codes */
40#define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS)
41
42/* describes the file as an image on the storage medium */
43static struct fileobj_binding
44{
45 struct file_base_binding bind; /* base info list item (first!) */
46 uint16_t flags; /* F(D)(O)_* bits of this file/dir */
47 uint16_t writers; /* number of writer streams */
48 struct filestr_cache cache; /* write mode shared cache */
49 file_size_t size; /* size of this file */
50 struct ll_head list; /* open streams for this file/dir */
51} fobindings[MAX_FILEOBJS];
52static struct mutex stream_mutexes[MAX_FILEOBJS] SHAREDBSS_ATTR;
53static struct ll_head free_bindings;
54static struct ll_head busy_bindings[NUM_VOLUMES];
55
56#define BUSY_BINDINGS(volume) \
57 (&busy_bindings[IF_MV_VOL(volume)])
58
59#define BASEBINDING_LIST(bindp) \
60 (BUSY_BINDINGS(BASEBINDING_VOL(bindp)))
61
62#define FREE_BINDINGS() \
63 (&free_bindings)
64
65#define BINDING_FIRST(type, volume...) \
66 ((struct fileobj_binding *)type##_BINDINGS(volume)->head)
67
68#define BINDING_NEXT(fobp) \
69 ((struct fileobj_binding *)(fobp)->bind.node.next)
70
71#define FOR_EACH_BINDING(volume, fobp) \
72 for (struct fileobj_binding *fobp = BINDING_FIRST(BUSY, volume); \
73 fobp; fobp = BINDING_NEXT(fobp))
74
75#define STREAM_FIRST(fobp) \
76 ((struct filestr_base *)(fobp)->list.head)
77
78#define STREAM_NEXT(s) \
79 ((struct filestr_base *)(s)->node.next)
80
81#define STREAM_THIS(s) \
82 (s)
83
84#define FOR_EACH_STREAM(what, start, s) \
85 for (struct filestr_base *s = STREAM_##what(start); \
86 s; s = STREAM_NEXT(s))
87
88/* once a file/directory, always a file/directory; such a change
89 is a bug */
90#define CHECK_FO_DIRECTORY(callflags, fobp) \
91 if (((callflags) ^ (fobp)->flags) & FO_DIRECTORY) \
92 { \
93 DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", \
94 __func__, (fobp), (callflags)); \
95 }
96
97/* syncs information for the stream's old and new parent directory if any are
98 currently opened */
99static void fileobj_sync_parent(const struct file_base_info *infop[],
100 int count)
101{
102 FOR_EACH_BINDING(infop[0]->volume, fobp)
103 {
104 if ((fobp->flags & (FO_DIRECTORY|FO_REMOVED)) != FO_DIRECTORY)
105 continue; /* not directory or removed can't be parent of anything */
106
107 struct filestr_base *parentstrp = STREAM_FIRST(fobp);
108 if (!parentstrp)
109 continue;
110
111 struct fat_file *parentfilep = &parentstrp->infop->fatfile;
112
113 for (int i = 0; i < count; i++)
114 {
115 if (!fat_dir_is_parent(parentfilep, &infop[i]->fatfile))
116 continue;
117
118 /* discard scan/read caches' parent dir info */
119 FOR_EACH_STREAM(THIS, parentstrp, s)
120 filestr_discard_cache(s);
121 }
122 }
123}
124
125/* see if this file has open streams and return that fileobj_binding if so,
126 else grab a new one from the free list; returns true if this is new */
127
128static bool binding_assign(const struct file_base_info *srcinfop,
129 struct fileobj_binding **fobpp)
130{
131 FOR_EACH_BINDING(srcinfop->fatfile.volume, fobp)
132 {
133 if (fobp->flags & FO_REMOVED)
134 continue;
135
136 if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile))
137 {
138 /* already has open streams/mounts*/
139 *fobpp = fobp;
140 return false;
141 }
142 }
143
144 /* not found - allocate anew */
145 *fobpp = BINDING_FIRST(FREE);
146 ll_remove_first(FREE_BINDINGS());
147 ll_init(&(*fobpp)->list);
148 return true;
149}
150
151/* mark descriptor as unused and return to the free list */
152static void binding_add_to_free_list(struct fileobj_binding *fobp)
153{
154 fobp->flags = 0;
155 ll_insert_last(FREE_BINDINGS(), &fobp->bind.node);
156}
157
158static void bind_source_info(const struct file_base_info *srcinfop,
159 struct fileobj_binding **fobpp)
160{
161 if (!binding_assign(srcinfop, fobpp))
162 return; /* already in use */
163 /* is new */
164 (*fobpp)->bind.info = *srcinfop;
165 fileobj_bind_file(&(*fobpp)->bind);
166}
167
168static void release_binding(struct fileobj_binding *fobp)
169{
170 fileobj_unbind_file(&fobp->bind);
171 binding_add_to_free_list(fobp);
172}
173
174/** File and directory internal interface **/
175
176void file_binding_insert_last(struct file_base_binding *bindp)
177{
178 ll_insert_last(BASEBINDING_LIST(bindp), &bindp->node);
179}
180
181void file_binding_remove(struct file_base_binding *bindp)
182{
183 ll_remove(BASEBINDING_LIST(bindp), &bindp->node);
184}
185
186#ifdef HAVE_DIRCACHE
187void file_binding_insert_first(struct file_base_binding *bindp)
188{
189 ll_insert_first(BASEBINDING_LIST(bindp), &bindp->node);
190}
191
192void file_binding_remove_next(struct file_base_binding *prevp,
193 struct file_base_binding *bindp)
194{
195 ll_remove_next(BASEBINDING_LIST(bindp), &prevp->node);
196 (void)bindp;
197}
198#endif /* HAVE_DIRCACHE */
199
200/* mounts a file object as a target from elsewhere */
201bool fileobj_mount(const struct file_base_info *srcinfop,
202 unsigned int callflags,
203 struct file_base_binding **bindpp)
204{
205 struct fileobj_binding *fobp;
206 bind_source_info(srcinfop, &fobp);
207 CHECK_FO_DIRECTORY(callflags, fobp);
208 if (fobp->flags & FO_MOUNTTARGET)
209 return false; /* already mounted */
210 fobp->flags |= FDO_BUSY | FO_MOUNTTARGET |
211 (callflags & FO_DIRECTORY);
212 *bindpp = &fobp->bind;
213 return true;
214}
215/* unmounts the file object and frees it if now unusued */
216void fileobj_unmount(struct file_base_binding *bindp)
217{
218 struct fileobj_binding *fobp = (struct fileobj_binding *)bindp;
219 if (!(fobp->flags & FO_MOUNTTARGET))
220 return; /* not mounted */
221 if (STREAM_FIRST(fobp) == NULL)
222 release_binding(fobp); /* no longer in use */
223 else
224 fobp->flags &= ~FO_MOUNTTARGET;
225}
226
227/* opens the file object for a new stream and sets up the caches;
228 * the stream must already be opened at the FS driver level and *stream
229 * initialized.
230 *
231 * NOTE: switches stream->infop to the one kept in common for all streams of
232 * the same file, making a copy for only the first stream
233 */
234void fileobj_fileop_open(struct filestr_base *stream,
235 const struct file_base_info *srcinfop,
236 unsigned int callflags)
237{
238 /* assign base file information */
239 struct fileobj_binding *fobp;
240 bind_source_info(srcinfop, &fobp);
241 unsigned int foflags = fobp->flags;
242
243 /* add stream to this file's list */
244 bool first = STREAM_FIRST(fobp) == NULL;
245 ll_insert_last(&fobp->list, &stream->node);
246
247 /* initiate the new stream into the enclave */
248 stream->flags = FDO_BUSY |
249 (callflags & (FF_MASK|FD_WRITE|FD_WRONLY|FD_APPEND));
250 stream->infop = &fobp->bind.info;
251 stream->fatstr.fatfilep = &fobp->bind.info.fatfile;
252 stream->bindp = &fobp->bind;
253 stream->mtx = &stream_mutexes[fobp - fobindings];
254
255 if (first)
256 {
257 /* first stream for file */
258 fobp->flags = foflags | FDO_BUSY | FO_SINGLE |
259 (callflags & (FO_DIRECTORY|FO_TRUNC));
260 fobp->writers = 0;
261 fobp->size = 0;
262 }
263 else
264 {
265 /* additional stream for file */
266 fobp->flags = (foflags & ~FO_SINGLE) | (callflags & FO_TRUNC);
267 CHECK_FO_DIRECTORY(callflags, fobp);
268 }
269
270 if ((callflags & FD_WRITE) && ++fobp->writers == 1)
271 {
272 /* first writer */
273 file_cache_init(&fobp->cache);
274 FOR_EACH_STREAM(FIRST, fobp, s)
275 filestr_assign_cache(s, &fobp->cache);
276 }
277 else if (fobp->writers)
278 {
279 /* already writers present */
280 filestr_assign_cache(stream, &fobp->cache);
281 }
282 else
283 {
284 /* another reader and no writers present */
285 file_cache_reset(stream->cachep);
286 }
287}
288
289/* close the stream and free associated resources */
290void fileobj_fileop_close(struct filestr_base *stream)
291{
292 if (!(stream->flags & FDO_BUSY))
293 {
294 /* not added to manager or forced-closed by unmounting */
295 filestr_base_destroy(stream);
296 return;
297 }
298
299 struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp;
300 unsigned int foflags = fobp->flags;
301
302 ll_remove(&fobp->list, &stream->node);
303
304 if (foflags & FO_SINGLE)
305 {
306 /* last stream for file; close everything */
307 if (fobp->writers)
308 file_cache_free(&fobp->cache);
309
310 /* binding must stay valid if something is mounted to here */
311 if (foflags & FO_MOUNTTARGET)
312 fobp->flags = foflags & (FDO_BUSY|FO_DIRECTORY|FO_MOUNTTARGET);
313 else
314 release_binding(fobp);
315 }
316 else
317 {
318 if ((stream->flags & FD_WRITE) && --fobp->writers == 0)
319 {
320 /* only readers remain; switch back to stream-local caching */
321 FOR_EACH_STREAM(FIRST, fobp, s)
322 filestr_copy_cache(s, &fobp->cache);
323
324 file_cache_free(&fobp->cache);
325 }
326
327 if (fobp->list.head == fobp->list.tail)
328 fobp->flags |= FO_SINGLE; /* only one open stream remaining */
329 }
330
331 filestr_base_destroy(stream);
332}
333
334/* informs manager that file has been created */
335void fileobj_fileop_create(struct filestr_base *stream,
336 const struct file_base_info *srcinfop,
337 unsigned int callflags)
338{
339 fileobj_fileop_open(stream, srcinfop, callflags);
340 fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1);
341}
342
343/* informs manager that file has been removed */
344void fileobj_fileop_remove(struct filestr_base *stream,
345 const struct file_base_info *oldinfop)
346{
347 ((struct fileobj_binding *)stream->bindp)->flags |= FO_REMOVED;
348 fileobj_sync_parent((const struct file_base_info *[]){ oldinfop }, 1);
349}
350
351/* informs manager that file has been renamed */
352void fileobj_fileop_rename(struct filestr_base *stream,
353 const struct file_base_info *oldinfop)
354{
355 /* if there is old info then this was a move and the old parent has to be
356 informed */
357 int count = oldinfop ? 2 : 1;
358 fileobj_sync_parent(&(const struct file_base_info *[])
359 { oldinfop, stream->infop }[2 - count],
360 count);
361}
362
363/* informs manager than directory entries have been updated */
364void fileobj_fileop_sync(struct filestr_base *stream)
365{
366 if (((struct fileobj_binding *)stream->bindp)->flags & FO_REMOVED)
367 return; /* no dir to sync */
368
369 fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1);
370}
371
372/* query for the pointer to the size storage for the file object */
373file_size_t * fileobj_get_sizep(const struct filestr_base *stream)
374{
375 if (!stream->bindp)
376 return NULL;
377
378 return &((struct fileobj_binding *)stream->bindp)->size;
379}
380
381/* iterate the list of streams for this stream's file */
382struct filestr_base * fileobj_get_next_stream(const struct filestr_base *stream,
383 const struct filestr_base *s)
384{
385 if (!stream->bindp)
386 return NULL;
387
388 return s ? STREAM_NEXT(s) : STREAM_FIRST((struct fileobj_binding *)stream->bindp);
389}
390
391/* query manager bitflags for the file object */
392unsigned int fileobj_get_flags(const struct filestr_base *stream)
393{
394 if (!stream->bindp)
395 return 0;
396
397 return ((struct fileobj_binding *)stream->bindp)->flags;
398}
399
400/* change manager bitflags for the file object (permitted only) */
401void fileobj_change_flags(struct filestr_base *stream,
402 unsigned int flags, unsigned int mask)
403{
404 struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp;
405 if (!fobp)
406 return;
407
408 mask &= FDO_CHG_MASK;
409 fobp->flags = (fobp->flags & ~mask) | (flags & mask);
410}
411
412/* mark all open streams on a device as "nonexistant" */
413void fileobj_mgr_unmount(IF_MV_NONVOID(int volume))
414{
415 FOR_EACH_VOLUME(volume, v)
416 {
417 struct fileobj_binding *fobp;
418 while ((fobp = BINDING_FIRST(BUSY, v)))
419 {
420 struct filestr_base *s;
421 while ((s = STREAM_FIRST(fobp)))
422 {
423 /* last ditch effort to preserve FS integrity; we could still
424 be alive (soft unmount, maybe); we get informed early */
425 fileop_onunmount_internal(s);
426
427 if (STREAM_FIRST(fobp) == s)
428 fileop_onclose_internal(s); /* above didn't close it */
429
430 /* keep it "busy" to avoid races; any valid file/directory
431 descriptor returned by an open call should always be
432 closed by whoever opened it (of course!) */
433 s->flags = (s->flags & ~FDO_BUSY) | FD_NONEXIST;
434 }
435 }
436 }
437}
438
439/* one-time init at startup */
440void fileobj_mgr_init(void)
441{
442 for (unsigned int i = 0; i < NUM_VOLUMES; i++)
443 ll_init(BUSY_BINDINGS(i));
444
445 ll_init(FREE_BINDINGS());
446 for (unsigned int i = 0; i < MAX_FILEOBJS; i++)
447 {
448 mutex_init(&stream_mutexes[i]);
449 binding_add_to_free_list(&fobindings[i]);
450 }
451}