2 * Copyright (c) 1999-2013 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <sys/types.h>
27 #include <sys/mount.h>
29 #include <sys/sysctl.h>
32 #include <sys/vnode.h>
34 #include <sys/ioctl.h>
50 #include <hfs/hfs_mount.h>
51 #include <hfs/hfs_format.h>
53 #include <TargetConditionals.h>
55 /* Sensible wrappers over the byte-swapping routines */
56 #include "hfs_endian.h"
57 #if !TARGET_OS_EMBEDDED
64 struct mntopt mopts
[] = {
66 MOPT_IGNORE_OWNERSHIP
,
72 #define HFS_MOUNT_TYPE "hfs"
74 gid_t a_gid
__P((char *));
75 uid_t a_uid
__P((char *));
76 mode_t a_mask
__P((char *));
77 struct hfs_mnt_encoding
* a_encoding
__P((char *));
78 int get_encoding_pref
__P((const char *));
79 int get_encoding_bias
__P((void));
80 unsigned int get_default_encoding(void);
82 void usage
__P((void));
86 int wrapper_requested
= 0;
88 typedef struct CreateDateAttrBuf
{
90 struct timespec creationTime
;
93 #define HFS_BLOCK_SIZE 512
96 * This is the straight GMT conversion constant:
97 * 00:00:00 January 1, 1970 - 00:00:00 January 1, 1904
98 * (3600 * 24 * ((365 * (1970 - 1904)) + (((1970 - 1904) / 4) + 1)))
100 #define MAC_GMT_FACTOR 2082844800UL
102 #define KEXT_LOAD_COMMAND "/sbin/kextload"
104 #define ENCODING_MODULE_PATH "/System/Library/Filesystems/hfs.fs/Encodings/"
106 #define MXENCDNAMELEN 16 /* Maximun length of encoding name string */
108 struct hfs_mnt_encoding
{
109 char encoding_name
[MXENCDNAMELEN
]; /* encoding type name */
110 u_int32_t encoding_id
; /* encoding type number */
115 * Lookup table for hfs encoding names
116 * Note: Names must be in alphabetical order
118 struct hfs_mnt_encoding hfs_mnt_encodinglist
[] = {
124 { "CentralEurRoman", 29 },
125 { "ChineseSimp", 25 },
126 { "ChineseTrad", 2 },
147 { "Roman", 0 }, /* default */
155 { "Ukrainian", 152 },
156 { "Vietnamese", 30 },
161 If path is a path to a block device, then return a path to the
162 corresponding raw device. Else return path unchanged.
164 const char *rawdevice(const char *path
)
166 const char *devdisk
= "/dev/disk";
167 static char raw
[MAXPATHLEN
];
169 if (!strncmp(path
, devdisk
, strlen(devdisk
))) {
170 /* The +5 below is strlen("/dev/"), so path+5 points to "disk..." */
171 int sn_len
= snprintf(raw
, sizeof(raw
), "/dev/r%s", path
+5);
173 /* error in building string. return original. */
177 if ((unsigned long) sn_len
< sizeof(raw
)) {
188 Return a pointer to the Master Directory Block or Volume Header Block
189 for the volume. In the case of an HFS volume with embedded HFS Plus
190 volume, this returns the HFS (wrapper) volume's Master Directory Block.
191 That is, the 512 bytes at offset 1024 bytes from the start of the given
194 The master block is cached globally. If it has previously been read in,
195 the cached copy will be returned. If this routine is called multiple times,
196 it must be called with the same device string.
199 device Path name to disk device (eg., "/dev/disk0s2")
202 A pointer to the MDB or VHB. This pointer may be in the middle of a
203 malloc'ed block. There may be more than 512 bytes of malloc'ed memory
204 at the returned address.
207 On error, this routine returns NULL.
209 void *GetMasterBlock(const char *device
)
211 static char *masterBlock
= NULL
;
220 * If we already read the master block, then just return it.
222 if (masterBlock
!= NULL
) {
226 device
= rawdevice(device
);
228 fd
= open(device
, O_RDONLY
| O_NDELAY
, 0);
230 fprintf(stderr
, "GetMasterBlock: Error %d opening %s\n", errno
, device
);
235 * Get the block size so we can read an entire block.
237 err
= ioctl(fd
, DKIOCGETBLOCKSIZE
, &blockSize
);
239 fprintf(stderr
, "GetMasterBlock: Error %d getting block size\n", errno
);
244 * Figure out the offset of the start of the block which contains
245 * byte offset 1024 (the start of the master block). This is 1024
246 * rounded down to a multiple of blockSize. But since blockSize is
247 * always a power of two, this will be either 0 (if blockSize > 1024)
248 * or 1024 (if blockSize <= 1024).
250 offset
= blockSize
> 1024 ? 0 : 1024;
253 * Allocate a buffer and read the block.
255 buf
= malloc(blockSize
);
257 fprintf(stderr
, "GetMasterBlock: Could not malloc %u bytes\n", blockSize
);
260 amount
= pread(fd
, buf
, blockSize
, offset
);
261 if (amount
!= blockSize
) {
262 fprintf(stderr
, "GetMasterBlock: Error %d from read; amount=%ld, wanted=%u\n", errno
, amount
, blockSize
);
267 * Point at the part of the buffer containing the master block.
268 * Then return that pointer.
270 * Note: if blockSize <= 1024, then offset = 1024, and the master
271 * block is at the start of the buffer. If blockSize > 1024, then
272 * offset = 0, and the master block is at offset 1024 from the start
275 masterBlock
= buf
+ 1024 - offset
;
276 buf
= NULL
; /* Don't free memory that masterBlock points into. */
287 u_int32_t
getVolumeCreateDate(const char *device
)
289 HFSMasterDirectoryBlock
* mdbPtr
;
290 u_int32_t volume_create_time
= 0;
292 mdbPtr
= GetMasterBlock(device
);
293 if (mdbPtr
== NULL
) goto exit
;
295 /* get the create date from the MDB (embedded case) or Volume Header */
296 if ((mdbPtr
->drSigWord
== SWAP_BE16 (kHFSSigWord
)) &&
297 (mdbPtr
->drEmbedSigWord
== SWAP_BE16 (kHFSPlusSigWord
))) {
299 volume_create_time
= SWAP_BE32 (mdbPtr
->drCrDate
);
301 } else if (mdbPtr
->drSigWord
== kHFSPlusSigWord
) {
302 HFSPlusVolumeHeader
* volHdrPtr
= (HFSPlusVolumeHeader
*) mdbPtr
;
304 volume_create_time
= SWAP_BE32 (volHdrPtr
->createDate
);
306 goto exit
; /* cound not match signature */
309 if (volume_create_time
> MAC_GMT_FACTOR
)
310 volume_create_time
-= MAC_GMT_FACTOR
;
312 volume_create_time
= 0; /* don't let date go negative! */
315 return volume_create_time
;
318 void syncCreateDate(const char *mntpt
, u_int32_t localCreateTime
)
322 struct attrlist attributes
;
323 CreateDateAttrBuf attrReturnBuffer
;
324 int64_t gmtCreateTime
;
326 int32_t newCreateTime
;
328 snprintf(path
, sizeof(path
), "%s/", mntpt
);
330 attributes
.bitmapcount
= ATTR_BIT_MAP_COUNT
;
331 attributes
.reserved
= 0;
332 attributes
.commonattr
= ATTR_CMN_CRTIME
;
333 attributes
.volattr
= 0;
334 attributes
.dirattr
= 0;
335 attributes
.fileattr
= 0;
336 attributes
.forkattr
= 0;
338 result
= getattrlist(path
, &attributes
, &attrReturnBuffer
, sizeof(attrReturnBuffer
), 0 );
341 gmtCreateTime
= attrReturnBuffer
.creationTime
.tv_sec
;
342 gmtOffset
= gmtCreateTime
- (int64_t) localCreateTime
+ 900;
344 gmtOffset
= 1800 * (gmtOffset
/ 1800);
346 gmtOffset
= -1800 * ((-gmtOffset
+ 1799) / 1800);
349 newCreateTime
= localCreateTime
+ gmtOffset
;
352 * if the root directory's create date doesn't match
353 * and its within +/- 15 seconds, then update it
355 if ((newCreateTime
!= attrReturnBuffer
.creationTime
.tv_sec
) &&
356 (( newCreateTime
- attrReturnBuffer
.creationTime
.tv_sec
) > -15) &&
357 ((newCreateTime
- attrReturnBuffer
.creationTime
.tv_sec
) < 15)) {
359 attrReturnBuffer
.creationTime
.tv_sec
= (time_t) newCreateTime
;
360 (void) setattrlist (path
,
362 &attrReturnBuffer
.creationTime
,
363 sizeof(attrReturnBuffer
.creationTime
),
370 * loads an hfs encoding converter module into the kernel
372 * Note: unloading of encoding converter modules is done in the kernel
375 load_encoding(struct hfs_mnt_encoding
*encp
)
381 char kmodfile
[MAXPATHLEN
];
383 /* MacRoman encoding (0) is built into the kernel */
384 if (encp
->encoding_id
== 0)
387 snprintf(kmodfile
, sizeof(kmodfile
), "%sHFS_Mac%s.kext", ENCODING_MODULE_PATH
, encp
->encoding_name
);
388 if (stat(kmodfile
, &sb
) == -1) {
389 fprintf(stdout
, "unable to find: %s\n", kmodfile
);
396 (void) execl(KEXT_LOAD_COMMAND
, KEXT_LOAD_COMMAND
, kmodfile
, NULL
);
398 exit(1); /* We can only get here if the exec failed */
399 } else if (pid
!= -1) {
400 if ((waitpid(pid
, (int *)&status
, 0) == pid
) && WIFEXITED(status
)) {
401 /* we attempted a load */
407 fprintf(stderr
, "unable to load: %s\n", kmodfile
);
418 struct hfs_mount_args args
;
420 char *dev
, dir
[MAXPATHLEN
];
422 struct timeval dummy_timeval
; /* gettimeofday() crashes if the first argument is NULL */
423 u_int32_t localCreateTime
;
424 struct hfs_mnt_encoding
*encp
;
426 #if TARGET_OS_EMBEDDED
427 mntflags
= MNT_NOATIME
;
432 (void)memset(&args
, '\0', sizeof(struct hfs_mount_args
));
435 * For a mount update, the following args must be explictly
436 * passed in as options to change their value. On a new
437 * mount, default values will be computed for all args.
440 args
.hfs_uid
= (uid_t
)VNOVAL
;
441 args
.hfs_gid
= (gid_t
)VNOVAL
;
442 args
.hfs_mask
= (mode_t
)VNOVAL
;
443 args
.hfs_encoding
= (u_int32_t
)VNOVAL
;
445 optind
= optreset
= 1; /* Reset for parse of new argv. */
446 while ((ch
= getopt(argc
, argv
, "xu:g:m:e:o:wt:jc")) != EOF
) {
450 unsigned long tbufsize
= strtoul(optarg
, &ptr
, 0);
451 if (tbufsize
>= UINT_MAX
) {
454 args
.journal_tbuffer_size
= (unsigned int) strtoul(optarg
, &ptr
, 0);
455 if ((args
.journal_tbuffer_size
== 0 ||
456 ((uint32_t) args
.journal_tbuffer_size
) == UINT_MAX
) && errno
!= 0) {
457 fprintf(stderr
, "%s: Invalid tbuffer size %s\n", argv
[0], optarg
);
461 args
.journal_tbuffer_size
*= 1024;
462 else if (*ptr
== 'm')
463 args
.journal_tbuffer_size
*= 1024*1024;
465 if (args
.flags
== VNOVAL
){
468 args
.flags
|= HFSFSMNT_EXTENDED_ARGS
;
472 /* disable the journal */
473 if(args
.flags
== VNOVAL
){
476 args
.flags
|= HFSFSMNT_EXTENDED_ARGS
;
477 args
.journal_disable
= 1;
480 // XXXdbg JOURNAL_NO_GROUP_COMMIT == 0x0001
481 args
.journal_flags
= 0x0001;
484 if (args
.flags
== VNOVAL
)
486 args
.flags
|= HFSFSMNT_NOXONFILES
;
489 args
.hfs_uid
= a_uid(optarg
);
492 args
.hfs_gid
= a_gid(optarg
);
495 args
.hfs_mask
= a_mask(optarg
);
498 encp
= a_encoding(optarg
);
503 getmntopts(optarg
, mopts
, &mntflags
, &dummy
);
507 if (args
.flags
== VNOVAL
)
509 args
.flags
|= HFSFSMNT_WRAPPER
;
510 wrapper_requested
= 1;
517 printf("mount_hfs: ERROR: unrecognized ch = '%c'\n", ch
);
523 if ((mntflags
& MNT_IGNORE_OWNERSHIP
) && !(mntflags
& MNT_UPDATE
)) {
525 * The defaults to be supplied in lieu of the on-disk permissions
526 * (could be overridden by explicit -u, -g, or -m options):
528 if (args
.hfs_uid
== (uid_t
)VNOVAL
) args
.hfs_uid
= UNKNOWNUID
;
529 if (args
.hfs_gid
== (gid_t
)VNOVAL
) args
.hfs_gid
= UNKNOWNGID
;
530 #if OVERRIDE_UNKNOWN_PERMISSIONS
531 if (args
.hfs_mask
== (mode_t
)VNOVAL
) args
.hfs_mask
= ACCESSPERMS
; /* 0777 */
539 printf("mount_hfs: ERROR: argc == %d != 2\n", argc
);
546 if (realpath(argv
[1], dir
) == NULL
)
547 err(1, "realpath %s", dir
);
551 /* HFS volumes need timezone info to convert local to GMT */
552 (void) gettimeofday( &dummy_timeval
, &args
.hfs_timezone
);
554 /* load requested encoding (if any) for hfs volume */
556 if (load_encoding(encp
) != 0)
557 exit(1); /* load failure */
558 args
.hfs_encoding
= encp
->encoding_id
;
562 * For a new mount (non-update case) fill in default values for all args
564 if ((mntflags
& MNT_UPDATE
) == 0) {
568 if (args
.flags
== VNOVAL
)
571 if ((args
.hfs_encoding
== (u_int32_t
)VNOVAL
) && (encp
== NULL
)) {
574 /* Find a suitable encoding preference. */
575 if ((encoding
= get_encoding_pref(dev
)) != -1) {
577 * Note: the encoding kext was loaded by
578 * hfs.util during the file system probe.
580 args
.hfs_encoding
= encoding
;
582 args
.hfs_encoding
= 0;
585 /* when the mountpoint is root, use default values */
586 if (strcmp(dir
, "/") == 0) {
591 /* otherwise inherit from the mountpoint */
592 } else if (stat(dir
, &sb
) == -1)
593 err(1, "stat %s", dir
);
595 if (args
.hfs_uid
== (uid_t
)VNOVAL
)
596 args
.hfs_uid
= sb
.st_uid
;
598 if (args
.hfs_gid
== (gid_t
)VNOVAL
)
599 args
.hfs_gid
= sb
.st_gid
;
601 if (args
.hfs_mask
== (mode_t
)VNOVAL
)
602 args
.hfs_mask
= sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
);
605 printf("mount_hfs: calling mount: \n" );
606 printf("\tdevice = %s\n", dev
);
607 printf("\tmount point = %s\n", dir
);
608 printf("\tmount flags = 0x%08x\n", mntflags
);
609 printf("\targ flags = 0x%x\n", args
.flags
);
610 printf("\tuid = %d\n", args
.hfs_uid
);
611 printf("\tgid = %d\n", args
.hfs_gid
);
612 printf("\tmode = %o\n", args
.hfs_mask
);
613 printf("\tencoding = %ld\n", args
.hfs_encoding
);
617 #if !TARGET_OS_EMBEDDED
619 * We shouldn't really be calling up to other layers, but
620 * an exception was made in this case to fix the situation
621 * where HFS was writable on optical media.
624 if ((_optical_is_writable(dev
) & _OPTICAL_WRITABLE_PACKET
)) {
625 mntflags
|= MNT_RDONLY
;
630 mntflags
|= MNT_RDONLY
;
632 if ((mntflags
& MNT_RDONLY
) == 0) {
634 * get the volume's create date so we can synchronize
635 * it with the root directory create date
637 localCreateTime
= getVolumeCreateDate(dev
);
643 if ((mountStatus
= mount(HFS_MOUNT_TYPE
, dir
, mntflags
, &args
)) < 0) {
645 printf("mount_hfs: error on mount(): error = %d.\n", mountStatus
);
651 * synchronize the root directory's create date
652 * with the volume's create date
655 syncCreateDate(dir
, localCreateTime
);
666 char *gname
, *orig
= s
;
671 for (gname
= s
; *s
&& isdigit(*s
); ++s
);
677 errx(1, "unknown group id: %s", orig
);
688 char *uname
, *orig
= s
;
693 for (uname
= s
; *s
&& isdigit(*s
); ++s
);
699 errx(1, "unknown user id: %s", orig
);
714 if (*s
>= '0' && *s
<= '7') {
716 rv
= strtol(optarg
, &ep
, 8);
718 if (!done
|| rv
< 0 || *ep
)
719 errx(1, "invalid file mode: %s", s
);
723 struct hfs_mnt_encoding
*
730 struct hfs_mnt_encoding
*p
, *q
, *enclist
;
731 int elements
= sizeof(hfs_mnt_encodinglist
) / sizeof(struct hfs_mnt_encoding
);
734 /* Use a binary search to find an encoding match */
735 p
= hfs_mnt_encodinglist
;
736 q
= p
+ (elements
- 1);
738 enclist
= p
+ ((q
- p
) >> 1); /* divide by 2 */
739 compare
= strcmp(s
, enclist
->encoding_name
);
742 else if (compare
> 0)
748 for (uname
= s
; *s
&& isdigit(*s
); ++s
);
750 if (*s
) goto unknown
;
752 encoding
= atoi(uname
);
753 for (i
=0, enclist
= hfs_mnt_encodinglist
; i
< elements
; i
++, enclist
++) {
754 if (enclist
->encoding_id
== encoding
)
759 errx(1, "unknown encoding: %s", uname
);
765 * Get file system's encoding preference.
768 get_encoding_pref(const char *device
)
770 struct hfs_mnt_encoding
*enclist
;
771 HFSMasterDirectoryBlock
* mdbp
;
776 mdbp
= GetMasterBlock(device
);
780 if (SWAP_BE16(mdbp
->drSigWord
) != kHFSSigWord
||
781 (SWAP_BE16(mdbp
->drEmbedSigWord
) == kHFSPlusSigWord
&& (!wrapper_requested
))) {
787 encoding
= GET_HFS_TEXT_ENCODING(SWAP_BE32(mdbp
->drFndrInfo
[4]));
789 if (encoding
== -1) {
790 encoding
= get_encoding_bias();
791 if (encoding
== 0 || encoding
== -1)
792 encoding
= get_default_encoding();
795 /* Check if this is a supported encoding. */
796 elements
= sizeof(hfs_mnt_encodinglist
) / sizeof(struct hfs_mnt_encoding
);
797 for (i
=0, enclist
= hfs_mnt_encodinglist
; i
< elements
; i
++, enclist
++) {
798 if (enclist
->encoding_id
== encoding
)
806 * Get kernel's encoding bias.
812 size_t buflen
= sizeof(int);
816 if (getvfsbyname("hfs", &vfc
) < 0)
820 mib
[1] = vfc
.vfc_typenum
;
821 mib
[2] = HFS_ENCODINGBIAS
;
823 if (sysctl(mib
, 3, &hint
, &buflen
, NULL
, 0) < 0)
830 #define __kCFUserEncodingFileName ("/.CFUserTextEncoding")
833 get_default_encoding()
835 struct passwd
*passwdp
;
837 if ((passwdp
= getpwuid(0))) { /* root account */
838 char buffer
[MAXPATHLEN
+ 1];
841 strlcpy(buffer
, passwdp
->pw_dir
, sizeof(buffer
));
842 strlcat(buffer
, __kCFUserEncodingFileName
, sizeof(buffer
));
844 if ((fd
= open(buffer
, O_RDONLY
, 0)) > 0) {
847 readSize
= read(fd
, buffer
, MAXPATHLEN
);
848 buffer
[(readSize
< 0 ? 0 : readSize
)] = '\0';
850 return strtol(buffer
, NULL
, 0);
853 return (0); /* Fallback to smRoman */
860 (void)fprintf(stderr
,
861 "usage: mount_hfs [-xw] [-u user] [-g group] [-m mask] [-e encoding] [-t tbuffer-size] [-j] [-c] [-o options] special-device filesystem-node\n");
862 (void)fprintf(stderr
, " -j disables journaling; -c disables group-commit for journaling\n");