A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 451 lines 15 kB view raw
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}