A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 707 lines 20 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2002 by Björn Stenberg 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 <stdio.h> 22#include <string.h> 23#include "config.h" 24#include "kernel.h" 25#include "storage.h" 26#include "debug.h" 27#include "disk_cache.h" 28#include "fileobj_mgr.h" 29#include "dir.h" 30#include "rb_namespace.h" 31#include "disk.h" 32#include "panic.h" 33 34#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) && !defined(BOOTLOADER) 35#include "bootdata.h" 36#include "crc32.h" 37#endif 38 39#ifndef CONFIG_DEFAULT_PARTNUM 40#define CONFIG_DEFAULT_PARTNUM 0 41#endif 42 43#define disk_reader_lock() file_internal_lock_READER() 44#define disk_reader_unlock() file_internal_unlock_READER() 45#define disk_writer_lock() file_internal_lock_WRITER() 46#define disk_writer_unlock() file_internal_unlock_WRITER() 47 48/* "MBR" Partition table entry layout: 49 ----------------------- 50 0: 0x80 - active 51 1: starting head 52 2: starting sector 53 3: starting cylinder 54 4: partition type 55 5: end head 56 6: end sector 57 7: end cylinder 58 8-11: starting sector (LBA) 59 12-15: nr of sectors in partition 60*/ 61 62#define BYTES2INT64(array, pos) \ 63 (((uint64_t)array[pos+0] << 0) | \ 64 ((uint64_t)array[pos+1] << 8) | \ 65 ((uint64_t)array[pos+2] << 16) | \ 66 ((uint64_t)array[pos+3] << 24) | \ 67 ((uint64_t)array[pos+4] << 32) | \ 68 ((uint64_t)array[pos+5] << 40) | \ 69 ((uint64_t)array[pos+6] << 48) | \ 70 ((uint64_t)array[pos+7] << 56) ) 71 72#define BYTES2INT32(array, pos) \ 73 (((uint32_t)array[pos+0] << 0) | \ 74 ((uint32_t)array[pos+1] << 8) | \ 75 ((uint32_t)array[pos+2] << 16) | \ 76 ((uint32_t)array[pos+3] << 24)) 77 78#define BYTES2INT16(array, pos) \ 79 (((uint16_t)array[pos+0] << 0) | \ 80 ((uint16_t)array[pos+1] << 8)) 81 82static struct partinfo part[NUM_DRIVES*MAX_PARTITIONS_PER_DRIVE]; 83static struct volumeinfo volumes[NUM_VOLUMES]; 84 85/* check if the entry points to a free volume */ 86static bool is_free_volume(const struct volumeinfo *vi) 87{ 88 return vi->drive < 0; 89} 90 91/* mark a volume entry as free */ 92static void mark_free_volume(struct volumeinfo *vi) 93{ 94 vi->drive = -1; 95 vi->partition = -1; 96} 97 98static int get_free_volume(void) 99{ 100 for (int i = 0; i < NUM_VOLUMES; i++) 101 if (is_free_volume(&volumes[i])) 102 return i; 103 104 return -1; /* none found */ 105} 106 107static void init_volume(struct volumeinfo *vi, int drive, int part) 108{ 109 vi->drive = drive; 110 vi->partition = part; 111} 112 113#ifdef MAX_VIRT_SECTOR_SIZE 114static uint8_t disk_sector_multiplier[NUM_DRIVES] = 115 { [0 ... NUM_DRIVES-1] = 1 }; 116 117int disk_get_sector_multiplier(IF_MD_NONVOID(int drive)) 118{ 119 if (!CHECK_DRV(drive)) 120 return 0; 121 122 disk_reader_lock(); 123 int multiplier = disk_sector_multiplier[IF_MD_DRV(drive)]; 124 disk_reader_unlock(); 125 return multiplier; 126} 127 128#ifdef DEFAULT_VIRT_SECTOR_SIZE 129void disk_set_sector_multiplier(IF_MD(int drive,) int mult) 130{ 131 if (!CHECK_DRV(drive)) 132 return; 133 134 disk_writer_lock(); 135 disk_sector_multiplier[IF_MD_DRV(drive)] = mult; 136 disk_writer_unlock(); 137} 138#endif /* DEFAULT_VIRT_SECTOR_SIZE */ 139#endif /* MAX_VIRT_SECTOR_SIZE */ 140 141#ifdef MAX_VARIABLE_LOG_SECTOR 142static uint16_t disk_log_sector_size[NUM_DRIVES] = 143 { [0 ... NUM_DRIVES-1] = SECTOR_SIZE }; /* Updated from STORAGE_INFO */ 144int disk_get_log_sector_size(IF_MD_NONVOID(int drive)) 145{ 146 if (!CHECK_DRV(drive)) 147 return 0; 148 149 disk_reader_lock(); 150 int size = disk_log_sector_size[IF_MD_DRV(drive)]; 151 disk_reader_unlock(); 152 return size; 153} 154#define LOG_SECTOR_SIZE(__drive) disk_log_sector_size[IF_MD_DRV(__drive)] 155#else /* !MAX_VARIABLE_LOG_SECTOR */ 156#define LOG_SECTOR_SIZE(__drive) SECTOR_SIZE 157#endif /* !MAX_VARIABLE_LOG_SECTOR */ 158 159bool disk_init(IF_MD_NONVOID(int drive)) 160{ 161 if (!CHECK_DRV(drive)) 162 return false; /* out of space in table */ 163 164 unsigned char *sector = dc_get_buffer(); 165 if (!sector) 166 return false; 167 168 /* Query logical sector size */ 169 struct storage_info *info = (struct storage_info*) sector; 170 storage_get_info(IF_MD_DRV(drive), info); 171 sector_t num_sectors = info->num_sectors; 172#ifdef DEFAULT_VIRT_SECTOR_SIZE 173 unsigned int sector_size = info->sector_size; 174#endif 175 176#if (CONFIG_STORAGE & STORAGE_ATA) 177 disk_writer_lock(); 178#ifdef MAX_VARIABLE_LOG_SECTOR 179 disk_log_sector_size[IF_MD_DRV(drive)] = info->sector_size; 180#endif 181 disk_writer_unlock(); 182 183#ifdef MAX_VARIABLE_LOG_SECTOR 184 if (info->sector_size > MAX_VARIABLE_LOG_SECTOR || info->sector_size > DC_CACHE_BUFSIZE) { 185 panicf("Unsupported logical sector size: %d", 186 info->sector_size); 187 } 188#else /* !MAX_VARIABLE_LOG_SECTOR */ 189 if (info->sector_size != SECTOR_SIZE) { 190 panicf("Unsupported logical sector size: %d", 191 info->sector_size); 192 } 193#endif /* !MAX_VARIABLE_LOG_SECTOR */ 194#endif /* STORAGE_ATA */ 195 196 memset(sector, 0, DC_CACHE_BUFSIZE); 197 storage_read_sectors(IF_MD(drive,) 0, 1, sector); 198 199 bool init = false; 200 201 /* check that the boot sector is initialized */ 202 if (BYTES2INT16(sector, 510) == 0xaa55) 203 { 204 /* For each drive, start at a different position, in order not to 205 destroy the first entry of drive 0. That one is needed to calculate 206 config sector position. */ 207 struct partinfo *pinfo = &part[IF_MD_DRV(drive)*MAX_PARTITIONS_PER_DRIVE]; 208 uint8_t is_gpt = 0; 209 210 disk_writer_lock(); 211 212 /* parse partitions */ 213 for (int i = 0; i < MAX_PARTITIONS_PER_DRIVE && i < 4; i++) 214 { 215 unsigned char* ptr = sector + 0x1be + 16*i; 216 pinfo[i].type = ptr[4]; 217 pinfo[i].start = BYTES2INT32(ptr, 8); 218 pinfo[i].size = BYTES2INT32(ptr, 12); 219 220 DEBUGF("Part%d: Type %02x, start: %08lx size: %08lx\n", 221 i,pinfo[i].type,pinfo[i].start,pinfo[i].size); 222 223 /* extended? */ 224 if ( pinfo[i].type == 0x05 || pinfo[i].type == 0x0f ) { 225 /* not handled yet */ 226 } 227 228 if (pinfo[i].type == PARTITION_TYPE_GPT_GUARD) { 229 is_gpt = 1; 230 } 231 232#if 0 // Currently done in disk_mount() upon successful mount only 233 /* Auto-correct partition entries */ 234 if (i == 0 && pinfo[i].type == 0 && 235 pinfo[i].start != 0 && pinfo[i].size != 0) { 236 pinfo[i].type = PARTITION_TYPE_FAT32_LBA; 237 // XXX consider correcting MBR and writing sector back? 238 } 239#endif 240 } 241 242 while (is_gpt) { 243 /* Re-start partition parsing using GPT */ 244 uint64_t part_lba; 245 uint32_t part_entry_size; 246 uint32_t part_entries = 0; 247 unsigned char* ptr = sector; 248 249#ifdef DEFAULT_VIRT_SECTOR_SIZE 250 const sector_t try_gpt[4] = { 1, num_sectors - 1, 251 (DEFAULT_VIRT_SECTOR_SIZE / sector_size), 252 (num_sectors / (DEFAULT_VIRT_SECTOR_SIZE / sector_size)) - 1 253 }; 254 255#else 256 sector_t try_gpt[2] = { 1, num_sectors - 1 }; 257#endif 258 259 for (unsigned int i = 0 ; i < (sizeof(try_gpt) / sizeof(try_gpt[0])) ; i++) { 260 storage_read_sectors(IF_MD(drive,) try_gpt[i], 1, sector); 261 part_lba = BYTES2INT64(ptr, 0); 262 if (part_lba == 0x5452415020494645ULL) { 263 part_entries = 1; 264#ifdef MAX_VIRT_SECTOR_SIZE 265 if (i >= 2) 266 disk_sector_multiplier[IF_MD_DRV(drive)] = try_gpt[2]; 267#endif 268 break; 269 } 270 } 271 if (!part_entries) { 272 DEBUGF("GPT: Invalid signature\n"); 273 break; 274 } 275 276 part_entry_size = BYTES2INT32(ptr, 8); 277 if (part_entry_size != 0x00010000) { 278 DEBUGF("GPT: Invalid version\n"); 279 break; 280 } 281 part_entry_size = BYTES2INT32(ptr, 12); 282 if (part_entry_size != 0x5c) { 283 DEBUGF("GPT: Invalid header size\n"); 284 break; 285 } 286 // XXX checksum header -- u32 @ offset 16 287 part_entry_size = BYTES2INT32(ptr, 24); 288 if (part_entry_size != 1) { 289 DEBUGF("GPT: Invalid header LBA\n"); 290 break; 291 } 292 293 part_lba = BYTES2INT64(ptr, 72); 294 part_entries = BYTES2INT32(ptr, 80); 295 part_entry_size = BYTES2INT32(ptr, 84); 296 297 int part = 0; 298reload: 299 storage_read_sectors(IF_MD(drive,) part_lba, 1, sector); 300 uint8_t *pptr = ptr; 301 while (part < MAX_PARTITIONS_PER_DRIVE && part_entries) { 302 if (pptr - ptr >= LOG_SECTOR_SIZE(drive)) { 303 part_lba++; 304 goto reload; 305 } 306 307 /* Parse GPT entry. We only care about the "General Data" type, ie: 308 EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 309 LE32 LE16 LE16 BE16 BE16 310 */ 311 uint64_t tmp; 312 tmp = BYTES2INT32(pptr, 0); 313 if (tmp != 0xEBD0A0A2) 314 goto skip; 315 tmp = BYTES2INT16(pptr, 4); 316 if (tmp != 0xB9E5) 317 goto skip; 318 tmp = BYTES2INT16(pptr, 6); 319 if (tmp != 0x4433) 320 goto skip; 321 if (pptr[8] != 0x87 || pptr[9] != 0xc0) 322 goto skip; 323 if (pptr[10] != 0x68 || pptr[11] != 0xb6 || pptr[12] != 0xb7 || 324 pptr[13] != 0x26 || pptr[14] != 0x99 || pptr[15] != 0xc7) 325 goto skip; 326 327 tmp = BYTES2INT64(pptr, 48); /* Flags */ 328 if (tmp) { 329 DEBUGF("GPT: Skip parition with flags\n"); 330 goto skip; /* Any flag makes us ignore this */ 331 } 332 tmp = BYTES2INT64(pptr, 32); /* FIRST LBA */ 333#ifndef STORAGE_64BIT_SECTOR 334 if (tmp > UINT32_MAX) { 335 DEBUGF("GPT: partition starts after 2TiB mark\n"); 336 goto skip; 337 } 338#endif 339 if (tmp < 34) { 340 DEBUGF("GPT: Invalid start LBA\n"); 341 goto skip; 342 } 343 pinfo[part].start = tmp; 344 tmp = BYTES2INT64(pptr, 40); /* LAST LBA */ 345#ifndef STORAGE_64BIT_SECTOR 346 if (tmp > UINT32_MAX) { 347 DEBUGF("GPT: partition ends after 2TiB mark\n"); 348 goto skip; 349 } 350#endif 351 if (tmp <= pinfo[part].start) { 352 DEBUGF("GPT: Invalid end LBA\n"); 353 goto skip; 354 } 355 pinfo[part].size = tmp - pinfo[part].start + 1; 356 pinfo[part].type = PARTITION_TYPE_FAT32_LBA; 357 358 DEBUGF("GPart%d: start: %016lx size: %016lx\n", 359 part,pinfo[part].start,pinfo[part].size); 360 part++; 361 362 skip: 363 pptr += part_entry_size; 364 part_entries--; 365 } 366 367 is_gpt = 0; /* To break out of the loop */ 368 } 369 disk_writer_unlock(); 370 371 init = true; 372 } 373 else 374 { 375 DEBUGF("Bad boot sector signature\n"); 376 } 377 378 dc_release_buffer(sector); 379 return init; 380} 381 382bool disk_partinfo(int partition, struct partinfo *info) 383{ 384 if (partition < 0 || partition >= (int)ARRAYLEN(part) || !info) 385 return false; 386 387 disk_reader_lock(); 388 *info = part[partition]; 389 disk_reader_unlock(); 390 return true; 391} 392 393int disk_mount(int drive) 394{ 395 int mounted = 0; /* reset partition-on-drive flag */ 396 397 disk_writer_lock(); 398 399 int volume = get_free_volume(); 400 401 if (volume < 0) 402 { 403 DEBUGF("No Free Volumes\n"); 404 disk_writer_unlock(); 405 return 0; 406 } 407 408 if (!disk_init(IF_MD(drive))) 409 { 410 disk_writer_unlock(); 411 return 0; 412 } 413 414 struct partinfo *pinfo = &part[IF_MD_DRV(drive)*MAX_PARTITIONS_PER_DRIVE]; 415#ifdef MAX_VIRT_SECTOR_SIZE 416 disk_sector_multiplier[IF_MD_DRV(drive)] = 1; 417#endif 418 419 /* try "superfloppy" mode */ 420 DEBUGF("Trying to mount sector 0.\n"); 421 422 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) 0)) 423 { 424#ifdef MAX_VIRT_SECTOR_SIZE 425 disk_sector_multiplier[drive] = fat_get_bytes_per_sector(IF_MV(volume)) / LOG_SECTOR_SIZE(drive); 426#endif 427 mounted = 1; 428 init_volume(&volumes[volume], drive, 0); 429 volume_onmount_internal(IF_MV(volume)); 430 431 struct storage_info info; 432 storage_get_info(drive, &info); 433 434 pinfo[0].type = PARTITION_TYPE_FAT32_LBA; 435 pinfo[0].start = 0; 436 pinfo[0].size = info.num_sectors; 437 } 438 439 if (mounted == 0 && volume != -1) /* not a "superfloppy"? */ 440 { 441 for (int i = CONFIG_DEFAULT_PARTNUM; 442 volume != -1 && i < MAX_PARTITIONS_PER_DRIVE && mounted < NUM_VOLUMES_PER_DRIVE; 443 i++) 444 { 445 if (pinfo[i].type == 0x05 || 446 pinfo[i].type == 0x0f || 447 (i != 0 && pinfo[i].type == 0)) 448 continue; /* skip free/extended partitions */ 449 450 DEBUGF("Trying to mount partition %d.\n", i); 451 452#ifdef MAX_VIRT_SECTOR_SIZE 453 for (int j = 1; j <= (MAX_VIRT_SECTOR_SIZE/LOG_SECTOR_SIZE(drive)); j <<= 1) 454 { 455 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start * j)) 456 { 457 pinfo[i].start *= j; 458 pinfo[i].size *= j; 459 mounted++; 460 init_volume(&volumes[volume], drive, i); 461 disk_sector_multiplier[drive] = j; 462 volume_onmount_internal(IF_MV(volume)); 463 volume = get_free_volume(); /* prepare next entry */ 464 if (pinfo[i].type == 0) { 465 pinfo[i].type = PARTITION_TYPE_FAT32_LBA; 466 // XXX write the sector back.? 467 } 468 break; 469 } 470 } 471#else /* ndef MAX_VIRT_SECTOR_SIZE */ 472 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start)) 473 { 474 mounted++; 475 init_volume(&volumes[volume], drive, i); 476 volume_onmount_internal(IF_MV(volume)); 477 volume = get_free_volume(); /* prepare next entry */ 478 if (pinfo[i].type == 0) { 479 pinfo[i].type = PARTITION_TYPE_FAT32_LBA; 480 // XXX write the sector back.? 481 } 482 } 483#endif /* MAX_VIRT_SECTOR_SIZE */ 484 } 485 486#if defined(MAX_VIRT_SECTOR_SIZE) && defined(MAX_PHYS_SECTOR_SIZE) 487 if (mounted) 488 ata_set_phys_sector_mult(disk_sector_multiplier[IF_MD_DRV(drive)]); 489#endif 490 } 491 492 disk_writer_unlock(); 493 return mounted; 494} 495 496int disk_mount_all(void) 497{ 498 int mounted = 0; 499 500 disk_writer_lock(); 501 502 /* reset all mounted partitions */ 503 volume_onunmount_internal(IF_MV(-1)); 504 fat_init(); 505 506 /* mark all volumes as free */ 507 for (int i = 0; i < NUM_VOLUMES; i++) 508 mark_free_volume(&volumes[i]); 509 510 for (int i = 0; i < NUM_DRIVES; i++) 511 { 512 #ifdef HAVE_HOTSWAP 513 if (storage_present(i)) 514 #endif 515 mounted += disk_mount(i); 516 } 517 518 disk_writer_unlock(); 519 return mounted; 520} 521 522int disk_unmount(int drive) 523{ 524 if (!CHECK_DRV(drive)) 525 return 0; 526 527 int unmounted = 0; 528 529 disk_writer_lock(); 530 531 for (int i = 0; i < NUM_VOLUMES; i++) 532 { 533 struct volumeinfo *vi = &volumes[i]; 534 /* unmount any volumes on the drive */ 535 if (vi->drive == drive) 536 { 537 mark_free_volume(vi); /* FIXME: should do this after unmount? */ 538 volume_onunmount_internal(IF_MV(i)); 539 fat_unmount(IF_MV(i)); 540 unmounted++; 541 } 542 } 543 544 disk_writer_unlock(); 545 return unmounted; 546} 547 548int disk_unmount_all(void) 549{ 550 int unmounted = 0; 551 552 disk_writer_lock(); 553 554 volume_onunmount_internal(IF_MV(-1)); 555 556 for (int i = 0; i < NUM_DRIVES; i++) 557 { 558 #ifdef HAVE_HOTSWAP 559 if (storage_present(i)) 560 #endif 561 unmounted += disk_unmount(i); 562 } 563 564 disk_writer_unlock(); 565 return unmounted; 566} 567 568bool disk_present(IF_MD_NONVOID(int drive)) 569{ 570 int rc = -1; 571 572 if (CHECK_DRV(drive)) 573 { 574 void *sector = dc_get_buffer(); 575 if (sector) 576 { 577 rc = storage_read_sectors(IF_MD(drive,) 0, 1, sector); 578 dc_release_buffer(sector); 579 } 580 } 581 582 return rc == 0; 583} 584 585 586/** Volume-centric functions **/ 587 588void volume_recalc_free(IF_MV_NONVOID(int volume)) 589{ 590 if (!CHECK_VOL(volume)) 591 return; 592 593 /* FIXME: this is crummy but the only way to ensure a correct freecount 594 if other threads are writing and changing the fsinfo; it is possible 595 to get multiple threads calling here and also writing and get correct 596 freespace counts, however a bit complicated to do; if thou desireth I 597 shall implement the concurrent version -- jethead71 */ 598 disk_writer_lock(); 599 fat_recalc_free(IF_MV(volume)); 600 disk_writer_unlock(); 601} 602 603unsigned int volume_get_cluster_size(IF_MV_NONVOID(int volume)) 604{ 605 if (!CHECK_VOL(volume)) 606 return 0; 607 608 disk_reader_lock(); 609 unsigned int clustersize = fat_get_cluster_size(IF_MV(volume)); 610 disk_reader_unlock(); 611 return clustersize; 612} 613 614void volume_size(IF_MV(int volume,) sector_t *sizep, sector_t *freep) 615{ 616 disk_reader_lock(); 617 618 if (!CHECK_VOL(volume) || !fat_size(IF_MV(volume,) sizep, freep)) 619 { 620 if (sizep) *sizep = 0; 621 if (freep) *freep = 0; 622 } 623 624 disk_reader_unlock(); 625} 626 627#if defined (HAVE_HOTSWAP) || defined (HAVE_MULTIDRIVE) \ 628 || defined (HAVE_DIRCACHE) || defined(HAVE_BOOTDATA) 629enum volume_info_type 630{ 631#ifdef HAVE_HOTSWAP 632 VP_REMOVABLE, 633 VP_PRESENT, 634#endif 635#if defined (HAVE_MULTIDRIVE) || defined (HAVE_DIRCACHE) 636 VP_DRIVE, 637#endif 638 VP_PARTITION, 639}; 640 641static int volume_properties(int volume, enum volume_info_type infotype) 642{ 643 int res = -1; 644 645 disk_reader_lock(); 646 647 if (CHECK_VOL(volume)) 648 { 649 struct volumeinfo *vi = &volumes[volume]; 650 switch (infotype) 651 { 652 #ifdef HAVE_HOTSWAP 653 case VP_REMOVABLE: 654 res = storage_removable(vi->drive) ? 1 : 0; 655 break; 656 case VP_PRESENT: 657 res = storage_present(vi->drive) ? 1 : 0; 658 break; 659 #endif 660 #if defined(HAVE_MULTIDRIVE) || defined(HAVE_DIRCACHE) 661 case VP_DRIVE: 662 res = vi->drive; 663 break; 664 #endif 665 case VP_PARTITION: 666 res = vi->partition; 667 break; 668 } 669 } 670 671 disk_reader_unlock(); 672 return res; 673} 674 675#ifdef HAVE_HOTSWAP 676bool volume_removable(int volume) 677{ 678 return volume_properties(volume, VP_REMOVABLE) > 0; 679} 680 681bool volume_present(int volume) 682{ 683 return volume_properties(volume, VP_PRESENT) > 0; 684} 685#endif /* HAVE_HOTSWAP */ 686 687#ifdef HAVE_MULTIDRIVE 688int volume_drive(int volume) 689{ 690 return volume_properties(volume, VP_DRIVE); 691} 692#endif /* HAVE_MULTIDRIVE */ 693 694int volume_partition(int volume) 695{ 696 return volume_properties(volume, VP_PARTITION); 697} 698 699#ifdef HAVE_DIRCACHE 700bool volume_ismounted(IF_MV_NONVOID(int volume)) 701{ 702 return volume_properties(IF_MV_VOL(volume), VP_DRIVE) >= 0; 703} 704#endif /* HAVE_DIRCACHE */ 705 706 707#endif /* HAVE_HOTSWAP || HAVE_MULTIDRIVE || HAVE_DIRCACHE || HAVE_BOOTDATA */