1/* 2 * Copyright (c) 2000-2007, 2009-2011 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23/* $NetBSD: mount_msdos.c,v 1.18 1997/09/16 12:24:18 lukem Exp $ */ 24 25/* 26 * Copyright (c) 1994 Christopher G. Demetriou 27 * All rights reserved. 28 * 29 * Redistribution and use in source and binary forms, with or without 30 * modification, are permitted provided that the following conditions 31 * are met: 32 * 1. Redistributions of source code must retain the above copyright 33 * notice, this list of conditions and the following disclaimer. 34 * 2. Redistributions in binary form must reproduce the above copyright 35 * notice, this list of conditions and the following disclaimer in the 36 * documentation and/or other materials provided with the distribution. 37 * 3. All advertising materials mentioning features or use of this software 38 * must display the following acknowledgement: 39 * This product includes software developed by Christopher G. Demetriou. 40 * 4. The name of the author may not be used to endorse or promote products 41 * derived from this software without specific prior written permission 42 * 43 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 44 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 45 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 46 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 47 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 48 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 49 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 50 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 51 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 52 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 53 */ 54 55#ifndef lint 56static const char rcsid[] = 57 "$FreeBSD: src/sbin/mount_msdos/mount_msdos.c,v 1.19 2000/01/08 16:47:55 ache Exp $"; 58#endif /* not lint */ 59 60/* Various system headers use standard int types */ 61#include <stdint.h> 62 63/* Get the boolean_t type. */ 64#include <mach/machine/boolean.h> 65 66#include <sys/param.h> 67#include <sys/mount.h> 68#include <sys/stat.h> 69#include <sys/sysctl.h> 70#include <sys/time.h> 71#include <sys/wait.h> 72 73#include "../msdosfs.kextproj/msdosfs.kmodproj/msdosfsmount.h" 74 75#include <ctype.h> 76#include <err.h> 77#include <errno.h> 78#include <grp.h> 79#include <pwd.h> 80#include <stdio.h> 81/* must be after stdio to declare fparseln */ 82#include <stdlib.h> 83#include <string.h> 84#include <sysexits.h> 85#include <unistd.h> 86#include <fcntl.h> 87#include <mntopts.h> 88 89#include <CoreFoundation/CFString.h> 90#include <CoreFoundation/CFStringEncodingExt.h> 91#include <CoreFoundation/CFNumber.h> 92 93#include <IOKit/IOKitLib.h> 94#include <IOKit/storage/IOMedia.h> 95#include <IOKit/storage/IOStorageProtocolCharacteristics.h> 96 97/* 98 * TODO: The -u, -g, and -m options could be expressed as ordinary mount 99 * options now that we're using the new getmntopts from libutil. That 100 * would make it possible to specify those options in /etc/fstab... 101 */ 102static struct mntopt mopts[] = { 103 MOPT_STDOPTS, 104 MOPT_FORCE, 105 MOPT_ASYNC, 106 MOPT_SYNC, 107 MOPT_DEFWRITE, 108 MOPT_NOATIME, 109 MOPT_UPDATE, 110 { NULL } 111}; 112 113static gid_t a_gid __P((char *)); 114static uid_t a_uid __P((char *)); 115static mode_t a_mask __P((char *)); 116static void usage __P((void)); 117 118static int checkLoadable(void); 119static char *progname; 120static int load_kmod(void); 121static void FindVolumeName(struct msdosfs_args *args); 122 123/* Taken from diskdev_cmds/disklib/getmntopts.c */ 124static void 125checkpath(const char *path, char resolved[MAXPATHLEN]) 126{ 127 struct stat sb; 128 129 if (realpath(path, resolved) != NULL && stat(resolved, &sb) == 0) { 130 if (!S_ISDIR(sb.st_mode)) 131 errx(EX_USAGE, "%s: not a directory", resolved); 132 } else 133 errx(EX_USAGE, "%s: %s", resolved, strerror(errno)); 134} 135 136/* Adapted from diskdev_cmds/disklib/getmntopts.c, with fixes. */ 137static void 138rmslashes(const char *rrpin, char rrpout[MAXPATHLEN]) 139{ 140 char *rrpoutstart; 141 142 for (rrpoutstart = rrpout; *rrpin != '\0'; *rrpout++ = *rrpin++) { 143 /* skip all double slashes */ 144 while (*rrpin == '/' && *(rrpin + 1) == '/') 145 rrpin++; 146 } 147 148 /* remove trailing slash if necessary */ 149 if (rrpout - rrpoutstart > 1 && *(rrpout - 1) == '/') 150 *(rrpout - 1) = '\0'; 151 else 152 *rrpout = '\0'; 153} 154 155 156/* 157 * Given a BSD disk name (eg., "disk0s3" or "disk1"), return non-zero if 158 * that disk is an internal disk, but not a removable disk. 159 */ 160static int disk_default_async(char *disk) 161{ 162 io_iterator_t iter; 163 kern_return_t err; 164 int result = 0; 165 166 err = IOServiceGetMatchingServices(kIOMasterPortDefault, 167 IOBSDNameMatching(kIOMasterPortDefault, 0, disk), &iter); 168 if (err == 0) 169 { 170 io_object_t obj; 171 obj = IOIteratorNext(iter); 172 if (obj) 173 { 174 CFBooleanRef removableRef; 175 CFDictionaryRef protocolCharacteristics; 176 protocolCharacteristics = IORegistryEntrySearchCFProperty(obj, 177 kIOServicePlane, 178 CFSTR(kIOPropertyProtocolCharacteristicsKey), 179 kCFAllocatorDefault, 180 kIORegistryIterateRecursively|kIORegistryIterateParents); 181 182 if (protocolCharacteristics && CFDictionaryGetTypeID() == CFGetTypeID(protocolCharacteristics)) 183 { 184 CFStringRef location; 185 location = CFDictionaryGetValue(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectLocationKey)); 186 if(location && CFStringGetTypeID() == CFGetTypeID(location)) 187 { 188 if(CFEqual(location, CFSTR(kIOPropertyInternalKey))) 189 result = 1; /* Internal => async */ 190 } 191 } 192 if (protocolCharacteristics) 193 CFRelease(protocolCharacteristics); 194 195 removableRef = IORegistryEntrySearchCFProperty(obj, 196 kIOServicePlane, 197 CFSTR(kIOMediaRemovableKey), 198 kCFAllocatorDefault, 199 kIORegistryIterateRecursively|kIORegistryIterateParents); 200 201 if (removableRef) 202 { 203 if (CFBooleanGetTypeID() == CFGetTypeID(removableRef)) 204 { 205 if (CFBooleanGetValue(removableRef)) 206 result = 0; /* Removable => not async */ 207 } 208 CFRelease(removableRef); 209 } 210 211 IOObjectRelease(obj); 212 } 213 214 IOObjectRelease(iter); 215 } 216 217 return result; 218} 219 220 221int 222main(argc, argv) 223 int argc; 224 char **argv; 225{ 226 struct msdosfs_args args; 227 struct stat sb; 228 int c, mntflags, set_gid, set_uid, set_mask; 229 mntoptparse_t mp; 230 char dev[MAXPATHLEN], mntpath[MAXPATHLEN]; 231 struct timezone local_tz; 232 char *options = "u:g:m:o:"; /* getopt options */ 233 234 mntflags = set_gid = set_uid = set_mask = 0; 235 (void)memset(&args, '\0', sizeof(args)); 236 args.magic = MSDOSFS_ARGSMAGIC; 237 progname = argv[0]; 238 239 /* 240 * Parse through the command line options once, in order to find the 241 * block device parameter. We'll need that to set the default value 242 * for MNT_ASYNC, before we parse the normal options (so MNT_ASYNC can 243 * be overridden either way via the command line). 244 */ 245 while (getopt(argc, argv, options) != -1) { 246 } 247 248 if (optind + 2 != argc) 249 usage(); 250 251 rmslashes(argv[optind], dev); 252 /* Check if <dev> is an internal, but not removable, drive; set MNT_ASYNC if so. */ 253 if (!strncmp(dev, "/dev/disk", 9) && disk_default_async(dev+5)) 254 mntflags |= MNT_ASYNC; 255 256 /* 257 * Now parse the options for real. 258 */ 259 optreset = 1; 260 optind = 1; 261 while ((c = getopt(argc, argv, options)) != -1) { 262 switch (c) { 263 case 'u': 264 args.uid = a_uid(optarg); 265 set_uid = 1; 266 break; 267 case 'g': 268 args.gid = a_gid(optarg); 269 set_gid = 1; 270 break; 271 case 'm': 272 args.mask = a_mask(optarg); 273 set_mask = 1; 274 break; 275 case 'o': 276 mp = getmntopts(optarg, mopts, &mntflags, (int *)&args.flags); 277 if (mp == NULL) 278 err(1, NULL); 279 freemntopts(mp); 280 break; 281 case '?': 282 default: 283 usage(); 284 break; 285 } 286 } 287 288 /* 289 * Resolve the mountpoint with realpath(3) and remove unnecessary 290 * slashes from the devicename if there are any. 291 */ 292 checkpath(argv[optind + 1], mntpath); 293 294 if (!set_gid || !set_uid || !set_mask) { 295 if (stat(mntpath, &sb) == -1) 296 err(EX_OSERR, "stat %s", mntpath); 297 298 if (!set_uid) 299 args.uid = sb.st_uid; 300 if (!set_gid) 301 args.gid = sb.st_gid; 302 if (!set_mask) 303 args.mask = sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); 304 305 /* If NO permisions are given, then set the whole mount to unknown */ 306 if (!set_gid && !set_uid && !set_mask) { 307 args.mask = ACCESSPERMS; 308 mntflags |= MNT_UNKNOWNPERMISSIONS; 309 } 310 } 311 312 args.fspec = dev; 313 /* Pass the number of seconds that local time (including DST) is west of GMT */ 314 gettimeofday(NULL, &local_tz); 315 args.secondsWest = local_tz.tz_minuteswest * 60 - 316 (local_tz.tz_dsttime ? 3600 : 0); 317 args.flags |= MSDOSFSMNT_SECONDSWEST; 318 319 if ((mntflags & MNT_UPDATE) == 0) { 320 FindVolumeName(&args); 321 } 322 323 if (checkLoadable()) /* Is it already loaded? */ 324 if (load_kmod()) /* Load it in */ 325 errx(EX_OSERR, "msdos filesystem is not available"); 326 327 if (mount("msdos", mntpath, mntflags, &args) < 0) 328 err(EX_OSERR, "%s on %s", dev, mntpath); 329 330 exit (0); 331} 332 333gid_t 334a_gid(s) 335 char *s; 336{ 337 struct group *gr; 338 char *gname; 339 gid_t gid; 340 341 if ((gr = getgrnam(s)) != NULL) 342 gid = gr->gr_gid; 343 else { 344 for (gname = s; *s && isdigit(*s); ++s); 345 if (!*s) 346 gid = atoi(gname); 347 else 348 errx(EX_NOUSER, "unknown group id: %s", gname); 349 } 350 return (gid); 351} 352 353uid_t 354a_uid(s) 355 char *s; 356{ 357 struct passwd *pw; 358 char *uname; 359 uid_t uid; 360 361 if ((pw = getpwnam(s)) != NULL) 362 uid = pw->pw_uid; 363 else { 364 for (uname = s; *s && isdigit(*s); ++s); 365 if (!*s) 366 uid = atoi(uname); 367 else 368 errx(EX_NOUSER, "unknown user id: %s", uname); 369 } 370 return (uid); 371} 372 373mode_t 374a_mask(s) 375 char *s; 376{ 377 int done; 378 mode_t rv; 379 char *ep; 380 381 done = 0; 382 rv = -1; 383 if (*s >= '0' && *s <= '7') { 384 done = 1; 385 rv = (mode_t)strtol(optarg, &ep, 8); 386 } 387 if (!done || rv < 0 || *ep) 388 errx(EX_USAGE, "invalid file mode: %s", s); 389 return (rv); 390} 391 392void 393usage() 394{ 395 fprintf(stderr, "%s\n%s\n", 396 "usage: mount_msdos [-o options] [-u user] [-g group] [-m mask]", 397 " [-s] [-l] [-9] bdev dir"); 398 exit(EX_USAGE); 399} 400 401 402#define FS_TYPE "msdos" 403 404/* Return non-zero if the file system is not yet loaded. */ 405static int checkLoadable(void) 406{ 407 int error; 408 struct vfsconf vfc; 409 410 error = getvfsbyname(FS_TYPE, &vfc); 411 412 return error; 413} 414 415 416#define LOAD_COMMAND "/sbin/kextload" 417#define MSDOS_MODULE_PATH "/System/Library/Extensions/msdosfs.kext" 418 419 420static int load_kmod(void) 421{ 422 423 int pid; 424 int result = -1; 425 union wait status; 426 427 pid = fork(); 428 if (pid == 0) { 429 result = execl(LOAD_COMMAND, LOAD_COMMAND, MSDOS_MODULE_PATH,NULL); 430 /* We can only get here if the exec failed */ 431 goto Err_Exit; 432 } 433 434 if (pid == -1) { 435 result = errno; 436 goto Err_Exit; 437 } 438 439 /* Success! */ 440 if ((wait4(pid, (int *)&status, 0, NULL) == pid) && (WIFEXITED(status))) { 441 result = status.w_retcode; 442 } 443 else { 444 result = -1; 445 } 446 447 448Err_Exit: 449 450 return (result); 451} 452 453/* 454 * Check a volume label. 455 */ 456static int 457oklabel(const char *src) 458{ 459 int c, i; 460 461 for (i = 0, c = 0; i <= 11; i++) { 462 c = (u_char)*src++; 463 if (c < ' ' + !i || strchr("\"*+,./:;<=>?[\\]|", c)) 464 break; 465 } 466 return i && !c; 467} 468 469static CFStringEncoding GetDefaultDOSEncoding(void) 470{ 471 CFStringEncoding encoding; 472 struct passwd *passwdp; 473 int fd; 474 ssize_t size; 475 char buffer[MAXPATHLEN + 1]; 476 477 /* 478 * Get a default (Mac) encoding. We use the CFUserTextEncoding 479 * file since CFStringGetSystemEncoding() always seems to 480 * return 0 when msdos.util is executed via disk arbitration. 481 */ 482 encoding = kCFStringEncodingMacRoman; /* Default to Roman/Latin */ 483 if ((passwdp = getpwuid(getuid()))) { 484 strlcpy(buffer, passwdp->pw_dir, sizeof(buffer)); 485 strlcat(buffer, "/.CFUserTextEncoding", sizeof(buffer)); 486 487 if ((fd = open(buffer, O_RDONLY, 0)) > 0) { 488 size = read(fd, buffer, MAXPATHLEN); 489 buffer[(size < 0 ? 0 : size)] = '\0'; 490 close(fd); 491 encoding = (CFStringEncoding)strtol(buffer, NULL, 0); 492 } 493 } 494 495 /* Convert the Mac encoding to a DOS/Windows encoding. */ 496 switch (encoding) { 497 case kCFStringEncodingMacRoman: 498 encoding = kCFStringEncodingDOSLatin1; 499 break; 500 case kCFStringEncodingMacJapanese: 501 encoding = kCFStringEncodingDOSJapanese; 502 break; 503 case kCFStringEncodingMacChineseTrad: 504 encoding = kCFStringEncodingDOSChineseTrad; 505 break; 506 case kCFStringEncodingMacKorean: 507 encoding = kCFStringEncodingDOSKorean; 508 break; 509 case kCFStringEncodingMacArabic: 510 encoding = kCFStringEncodingDOSArabic; 511 break; 512 case kCFStringEncodingMacHebrew: 513 encoding = kCFStringEncodingDOSHebrew; 514 break; 515 case kCFStringEncodingMacGreek: 516 encoding = kCFStringEncodingDOSGreek; 517 break; 518 case kCFStringEncodingMacCyrillic: 519 case kCFStringEncodingMacUkrainian: 520 encoding = kCFStringEncodingDOSCyrillic; 521 break; 522 case kCFStringEncodingMacThai: 523 encoding = kCFStringEncodingDOSThai; 524 break; 525 case kCFStringEncodingMacChineseSimp: 526 encoding = kCFStringEncodingDOSChineseSimplif; 527 break; 528 case kCFStringEncodingMacCentralEurRoman: 529 case kCFStringEncodingMacCroatian: 530 case kCFStringEncodingMacRomanian: 531 encoding = kCFStringEncodingDOSLatin2; 532 break; 533 case kCFStringEncodingMacTurkish: 534 encoding = kCFStringEncodingDOSTurkish; 535 break; 536 case kCFStringEncodingMacIcelandic: 537 encoding = kCFStringEncodingDOSIcelandic; 538 break; 539 case kCFStringEncodingMacFarsi: 540 encoding = kCFStringEncodingDOSArabic; 541 break; 542 default: 543 encoding = kCFStringEncodingInvalidId; /* Error: no corresponding Windows encoding */ 544 break; 545 } 546 547 return encoding; 548} 549 550#define MAX_DOS_BLOCKSIZE 4096 551 552struct dosdirentry { 553 u_int8_t name[11]; 554 u_int8_t attr; 555 u_int8_t reserved; 556 u_int8_t createTimeTenth; 557 u_int16_t createTime; 558 u_int16_t createDate; 559 u_int16_t accessDate; 560 u_int16_t clusterHi; 561 u_int16_t modTime; 562 u_int16_t modDate; 563 u_int16_t clusterLo; 564 u_int32_t size; 565}; 566#define ATTR_VOLUME_NAME 0x08 567#define ATTR_VOLUME_MASK 0x18 568#define ATTR_LONG_NAME 0x0F 569#define ATTR_MASK 0x3F 570 571#define SLOT_EMPTY 0x00 572#define SLOT_DELETED 0xE5U 573#define SLOT_E5 0x05 574 575#define CLUST_FIRST 2 576#define CLUST_RESERVED 0x0FFFFFF7 577 578static void FindVolumeName(struct msdosfs_args *args) 579{ 580 int fd; 581 u_int32_t i; 582 struct dosdirentry *dir; 583 void *rootBuffer; 584 unsigned bytesPerSector; 585 unsigned sectorsPerCluster; 586 unsigned rootDirEntries; 587 unsigned reservedSectors; 588 unsigned numFATs; 589 u_int32_t sectorsPerFAT; 590 off_t readOffset; /* Byte offset of current sector */ 591 ssize_t readAmount; 592 unsigned char buf[MAX_DOS_BLOCKSIZE]; 593 char label[12]; 594 CFStringRef cfstr; 595 596 bzero(label, sizeof(label)); /* Default to no label */ 597 rootBuffer = NULL; 598 599 fd = open(args->fspec, O_RDONLY, 0); 600 if (fd<0) 601 err(EX_OSERR, "%s", args->fspec); 602 603 /* Read the boot sector */ 604 if (pread(fd, buf, MAX_DOS_BLOCKSIZE, 0) != MAX_DOS_BLOCKSIZE) 605 err(EX_OSERR, "%s", args->fspec); 606 607 /* Check the jump field (first 3 bytes)? */ 608 609 /* Get the bytes per sector */ 610 bytesPerSector = buf[11] + buf[12]*256; 611 if (bytesPerSector < 512 || bytesPerSector > MAX_DOS_BLOCKSIZE || (bytesPerSector & (bytesPerSector-1))) 612 errx(EX_OSERR, "Unsupported sector size (%u)", bytesPerSector); 613 614 /* Get the sectors per cluster */ 615 sectorsPerCluster = buf[13]; 616 if (sectorsPerCluster==0 || (sectorsPerCluster & (sectorsPerCluster-1))) 617 errx(EX_OSERR, "Unsupported sectors per cluster (%u)", sectorsPerCluster); 618 619 reservedSectors = buf[14] + buf[15]*256; 620 numFATs = buf[16]; 621 622 /* Get the size of the root directory, in sectors */ 623 rootDirEntries = buf[17] + buf[18]*256; 624 625 /* If there is a label in the boot parameter block, copy it */ 626 if (rootDirEntries == 0) { 627 bcopy(&buf[71], label, 11); 628 } else { 629 if (buf[38] == 0x29) 630 bcopy(&buf[43], label, 11); 631 } 632 633 /* If there is a label in the root directory, copy it */ 634 if (rootDirEntries != 0) { 635 /* FAT12 or FAT16 */ 636 u_int32_t firstRootSector; 637 638 sectorsPerFAT = buf[22] + buf[23]*256; 639 firstRootSector = reservedSectors + numFATs * sectorsPerFAT; 640 641 readOffset = firstRootSector * bytesPerSector; 642 readAmount = (rootDirEntries * sizeof(struct dosdirentry) + bytesPerSector-1) / bytesPerSector; 643 readAmount *= bytesPerSector; 644 645 rootBuffer = malloc(readAmount); 646 if (rootBuffer == NULL) 647 errx(EX_OSERR, "Out of memory"); 648 649 /* Read the root directory */ 650 if (pread(fd, rootBuffer, readAmount, readOffset) != readAmount) 651 err(EX_OSERR, "%s", args->fspec); 652 653 /* Loop over root directory entries */ 654 for (i=0,dir=rootBuffer; i<rootDirEntries; ++i,++dir) { 655 if (dir->name[0] == SLOT_EMPTY) 656 goto end_of_dir; 657 if (dir->name[0] == SLOT_DELETED) 658 continue; 659 if ((dir->attr & ATTR_MASK) == ATTR_LONG_NAME) 660 continue; 661 if ((dir->attr & ATTR_VOLUME_MASK) == ATTR_VOLUME_NAME) { 662 bcopy(dir->name, label, 11); 663 goto end_of_dir; 664 } 665 } 666 } else { 667 /* FAT32 */ 668 u_int32_t cluster; /* Current cluster number */ 669 u_int32_t clusterOffset; /* Sector where cluster data starts */ 670 671 sectorsPerFAT = buf[36] + (buf[37]<<8L) + (buf[38]<<16L) + (buf[39]<<24L); 672 clusterOffset = reservedSectors + numFATs * sectorsPerFAT; 673 674 readAmount = bytesPerSector * sectorsPerCluster; 675 rootBuffer = malloc(readAmount); 676 if (rootBuffer == NULL) 677 errx(EX_OSERR, "Out of memory"); 678 679 /* Figure out the number of directory entries per cluster */ 680 rootDirEntries = (unsigned)(readAmount / sizeof(struct dosdirentry)); 681 682 /* Start with the first cluster of the root directory */ 683 cluster = buf[44] + (buf[45]<<8L) + (buf[46]<<16L) + (buf[47]<<24L); 684 685 /* Loop over clusters in the root directory */ 686 while (cluster >= CLUST_FIRST && cluster < CLUST_RESERVED) { 687 readOffset = (cluster - CLUST_FIRST) * sectorsPerCluster + clusterOffset; 688 readOffset *= bytesPerSector; 689 690 /* Read the cluster */ 691 if (pread(fd, rootBuffer, readAmount, readOffset) != readAmount) 692 err(EX_OSERR, "%s", args->fspec); 693 694 /* Loop over every directory entry in the cluster */ 695 for (i=0,dir=rootBuffer; i<rootDirEntries; ++i,++dir) { 696 if (dir->name[0] == SLOT_EMPTY) 697 goto end_of_dir; 698 if (dir->name[0] == SLOT_DELETED) 699 continue; 700 if ((dir->attr & ATTR_MASK) == ATTR_LONG_NAME) 701 continue; 702 if ((dir->attr & ATTR_VOLUME_MASK) == ATTR_VOLUME_NAME) { 703 bcopy(dir->name, label, 11); 704 goto end_of_dir; 705 } 706 } 707 708 /* Read the FAT so we can find the next cluster */ 709 readOffset = reservedSectors + ((cluster * 4) / bytesPerSector); 710 readOffset *= bytesPerSector; 711 712 if (pread(fd, buf, bytesPerSector, readOffset) != bytesPerSector) 713 err(EX_OSERR, "%s", args->fspec); 714 715 /* Determine byte offset in FAT sector for "cluster" */ 716 i = (cluster * 4) % bytesPerSector; 717 cluster = buf[i] + (buf[i+1]<<8L) + (buf[i+2]<<16L) + (buf[i+3]<<24L); 718 cluster &= 0x0FFFFFFF; /* Ignore the reserved upper bits */ 719 } 720 } 721 722end_of_dir: 723 if (rootBuffer) 724 free(rootBuffer); 725 close(fd); 726 727 /* Convert a leading 0x05 to 0xE5 for multibyte encodings */ 728 if (label[0] == 0x05) 729 label[0] = 0xE5; 730 731 /* Check for illegal characters */ 732 if (!oklabel(label)) 733 label[0] = 0; 734 735 /* Remove any trailing spaces. */ 736 i = 11; 737 do { 738 --i; 739 if (label[i] == ' ') 740 label[i] = 0; 741 else 742 break; 743 } while (i != 0); 744 745 /* Convert using default encoding, or Latin1 */ 746 cfstr = CFStringCreateWithCString(NULL, label, GetDefaultDOSEncoding()); 747 if (cfstr == NULL) 748 cfstr = CFStringCreateWithCString(NULL, label, kCFStringEncodingDOSLatin1); 749 if (cfstr == NULL) 750 args->label[0] = 0; 751 else { 752 CFMutableStringRef mutable; 753 754 mutable = CFStringCreateMutableCopy(NULL, 0, cfstr); 755 if (mutable != NULL) { 756 CFStringNormalize(mutable, kCFStringNormalizationFormD); 757 CFStringGetCString(mutable, (char *)args->label, sizeof(args->label), kCFStringEncodingUTF8); 758 CFRelease(mutable); 759 } 760 761 CFRelease(cfstr); 762 } 763 args->flags |= MSDOSFSMNT_LABEL; 764} 765