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