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
65 * Replay the journal. We don't care if there are problems.
68 replay_journal(const char *device
)
74 fd
= open(device
, O_RDWR
);
76 warn("Could not open block device %s for writing", device
);
79 if (getvfsbyname("hfs", &vfc
) != 0) {
80 warn("Could not get hfs vfs information");
84 mib
[1] = vfc
.vfc_typenum
;
85 mib
[2] = HFS_REPLAY_JOURNAL
;
87 (void)sysctl(mib
, 4, NULL
, NULL
, NULL
, 0);
95 struct mntopt mopts
[] = {
97 MOPT_IGNORE_OWNERSHIP
,
103 #define HFS_MOUNT_TYPE "hfs"
105 gid_t a_gid
__P((char *));
106 uid_t a_uid
__P((char *));
107 mode_t a_mask
__P((char *));
108 struct hfs_mnt_encoding
* a_encoding
__P((char *));
109 int get_encoding_pref
__P((const char *));
110 int get_encoding_bias
__P((void));
111 unsigned int get_default_encoding(void);
113 void usage
__P((void));
117 int wrapper_requested
= 0;
119 typedef struct CreateDateAttrBuf
{
121 struct timespec creationTime
;
124 #define HFS_BLOCK_SIZE 512
127 * This is the straight GMT conversion constant:
128 * 00:00:00 January 1, 1970 - 00:00:00 January 1, 1904
129 * (3600 * 24 * ((365 * (1970 - 1904)) + (((1970 - 1904) / 4) + 1)))
131 #define MAC_GMT_FACTOR 2082844800UL
133 #define KEXT_LOAD_COMMAND "/sbin/kextload"
135 #define ENCODING_MODULE_PATH "/System/Library/Filesystems/hfs.fs/Contents/Resources/Encodings/"
137 #define MXENCDNAMELEN 16 /* Maximun length of encoding name string */
139 struct hfs_mnt_encoding
{
140 char encoding_name
[MXENCDNAMELEN
]; /* encoding type name */
141 u_int32_t encoding_id
; /* encoding type number */
146 * Lookup table for hfs encoding names
147 * Note: Names must be in alphabetical order
149 struct hfs_mnt_encoding hfs_mnt_encodinglist
[] = {
155 { "CentralEurRoman", 29 },
156 { "ChineseSimp", 25 },
157 { "ChineseTrad", 2 },
178 { "Roman", 0 }, /* default */
186 { "Ukrainian", 152 },
187 { "Vietnamese", 30 },
192 If path is a path to a block device, then return a path to the
193 corresponding raw device. Else return path unchanged.
195 const char *rawdevice(const char *path
)
197 const char *devdisk
= "/dev/disk";
198 static char raw
[MAXPATHLEN
];
200 if (!strncmp(path
, devdisk
, strlen(devdisk
))) {
201 /* The +5 below is strlen("/dev/"), so path+5 points to "disk..." */
202 int sn_len
= snprintf(raw
, sizeof(raw
), "/dev/r%s", path
+5);
204 /* error in building string. return original. */
208 if ((unsigned long) sn_len
< sizeof(raw
)) {
219 Return a pointer to the Master Directory Block or Volume Header Block
220 for the volume. In the case of an HFS volume with embedded HFS Plus
221 volume, this returns the HFS (wrapper) volume's Master Directory Block.
222 That is, the 512 bytes at offset 1024 bytes from the start of the given
225 The master block is cached globally. If it has previously been read in,
226 the cached copy will be returned. If this routine is called multiple times,
227 it must be called with the same device string.
230 device Path name to disk device (eg., "/dev/disk0s2")
233 A pointer to the MDB or VHB. This pointer may be in the middle of a
234 malloc'ed block. There may be more than 512 bytes of malloc'ed memory
235 at the returned address.
238 On error, this routine returns NULL.
240 void *GetMasterBlock(const char *device
)
242 static char *masterBlock
= NULL
;
251 * If we already read the master block, then just return it.
253 if (masterBlock
!= NULL
) {
257 device
= rawdevice(device
);
259 fd
= open(device
, O_RDONLY
| O_NDELAY
, 0);
261 fprintf(stderr
, "GetMasterBlock: Error %d opening %s\n", errno
, device
);
266 * Get the block size so we can read an entire block.
268 err
= ioctl(fd
, DKIOCGETBLOCKSIZE
, &blockSize
);
270 fprintf(stderr
, "GetMasterBlock: Error %d getting block size\n", errno
);
275 * Figure out the offset of the start of the block which contains
276 * byte offset 1024 (the start of the master block). This is 1024
277 * rounded down to a multiple of blockSize. But since blockSize is
278 * always a power of two, this will be either 0 (if blockSize > 1024)
279 * or 1024 (if blockSize <= 1024).
281 offset
= blockSize
> 1024 ? 0 : 1024;
284 * Allocate a buffer and read the block.
286 buf
= malloc(blockSize
);
288 fprintf(stderr
, "GetMasterBlock: Could not malloc %u bytes\n", blockSize
);
291 amount
= pread(fd
, buf
, blockSize
, offset
);
292 if (amount
!= blockSize
) {
293 fprintf(stderr
, "GetMasterBlock: Error %d from read; amount=%ld, wanted=%u\n", errno
, amount
, blockSize
);
298 * Point at the part of the buffer containing the master block.
299 * Then return that pointer.
301 * Note: if blockSize <= 1024, then offset = 1024, and the master
302 * block is at the start of the buffer. If blockSize > 1024, then
303 * offset = 0, and the master block is at offset 1024 from the start
306 masterBlock
= buf
+ 1024 - offset
;
307 buf
= NULL
; /* Don't free memory that masterBlock points into. */
318 u_int32_t
getVolumeCreateDate(const char *device
)
320 HFSMasterDirectoryBlock
* mdbPtr
;
321 u_int32_t volume_create_time
= 0;
323 mdbPtr
= GetMasterBlock(device
);
324 if (mdbPtr
== NULL
) goto exit
;
326 /* get the create date from the MDB (embedded case) or Volume Header */
327 if ((mdbPtr
->drSigWord
== SWAP_BE16 (kHFSSigWord
)) &&
328 (mdbPtr
->drEmbedSigWord
== SWAP_BE16 (kHFSPlusSigWord
))) {
330 volume_create_time
= SWAP_BE32 (mdbPtr
->drCrDate
);
332 } else if (mdbPtr
->drSigWord
== kHFSPlusSigWord
) {
333 HFSPlusVolumeHeader
* volHdrPtr
= (HFSPlusVolumeHeader
*) mdbPtr
;
335 volume_create_time
= SWAP_BE32 (volHdrPtr
->createDate
);
337 goto exit
; /* cound not match signature */
340 if (volume_create_time
> MAC_GMT_FACTOR
)
341 volume_create_time
-= MAC_GMT_FACTOR
;
343 volume_create_time
= 0; /* don't let date go negative! */
346 return volume_create_time
;
349 void syncCreateDate(const char *mntpt
, u_int32_t localCreateTime
)
353 struct attrlist attributes
;
354 CreateDateAttrBuf attrReturnBuffer
;
355 int64_t gmtCreateTime
;
357 int32_t newCreateTime
;
359 snprintf(path
, sizeof(path
), "%s/", mntpt
);
361 attributes
.bitmapcount
= ATTR_BIT_MAP_COUNT
;
362 attributes
.reserved
= 0;
363 attributes
.commonattr
= ATTR_CMN_CRTIME
;
364 attributes
.volattr
= 0;
365 attributes
.dirattr
= 0;
366 attributes
.fileattr
= 0;
367 attributes
.forkattr
= 0;
369 result
= getattrlist(path
, &attributes
, &attrReturnBuffer
, sizeof(attrReturnBuffer
), 0 );
372 gmtCreateTime
= attrReturnBuffer
.creationTime
.tv_sec
;
373 gmtOffset
= gmtCreateTime
- (int64_t) localCreateTime
+ 900;
375 gmtOffset
= 1800 * (gmtOffset
/ 1800);
377 gmtOffset
= -1800 * ((-gmtOffset
+ 1799) / 1800);
380 newCreateTime
= localCreateTime
+ gmtOffset
;
383 * if the root directory's create date doesn't match
384 * and its within +/- 15 seconds, then update it
386 if ((newCreateTime
!= attrReturnBuffer
.creationTime
.tv_sec
) &&
387 (( newCreateTime
- attrReturnBuffer
.creationTime
.tv_sec
) > -15) &&
388 ((newCreateTime
- attrReturnBuffer
.creationTime
.tv_sec
) < 15)) {
390 attrReturnBuffer
.creationTime
.tv_sec
= (time_t) newCreateTime
;
391 (void) setattrlist (path
,
393 &attrReturnBuffer
.creationTime
,
394 sizeof(attrReturnBuffer
.creationTime
),
401 * loads an hfs encoding converter module into the kernel
403 * Note: unloading of encoding converter modules is done in the kernel
406 load_encoding(struct hfs_mnt_encoding
*encp
)
412 char kmodfile
[MAXPATHLEN
];
414 /* MacRoman encoding (0) is built into the kernel */
415 if (encp
->encoding_id
== 0)
418 snprintf(kmodfile
, sizeof(kmodfile
), "%sHFS_Mac%s.kext", ENCODING_MODULE_PATH
, encp
->encoding_name
);
419 if (stat(kmodfile
, &sb
) == -1) {
420 fprintf(stdout
, "unable to find: %s\n", kmodfile
);
427 (void) execl(KEXT_LOAD_COMMAND
, KEXT_LOAD_COMMAND
, kmodfile
, NULL
);
429 exit(1); /* We can only get here if the exec failed */
430 } else if (pid
!= -1) {
431 if ((waitpid(pid
, (int *)&status
, 0) == pid
) && WIFEXITED(status
)) {
432 /* we attempted a load */
438 fprintf(stderr
, "unable to load: %s\n", kmodfile
);
449 struct hfs_mount_args args
;
451 char *dev
, dir
[MAXPATHLEN
];
453 struct timeval dummy_timeval
; /* gettimeofday() crashes if the first argument is NULL */
454 u_int32_t localCreateTime
;
455 struct hfs_mnt_encoding
*encp
;
458 int tmp_mntflags
= 0;
459 #if TARGET_OS_EMBEDDED
460 mntflags
= MNT_NOATIME
;
465 (void)memset(&args
, '\0', sizeof(struct hfs_mount_args
));
468 * For a mount update, the following args must be explictly
469 * passed in as options to change their value. On a new
470 * mount, default values will be computed for all args.
473 args
.hfs_uid
= (uid_t
)VNOVAL
;
474 args
.hfs_gid
= (gid_t
)VNOVAL
;
475 args
.hfs_mask
= (mode_t
)VNOVAL
;
476 args
.hfs_encoding
= (u_int32_t
)VNOVAL
;
478 optind
= optreset
= 1; /* Reset for parse of new argv. */
479 while ((ch
= getopt(argc
, argv
, "xu:g:m:e:o:wt:jc")) != EOF
) {
483 unsigned long tbufsize
= strtoul(optarg
, &ptr
, 0);
484 if (tbufsize
>= UINT_MAX
) {
487 args
.journal_tbuffer_size
= (unsigned int) strtoul(optarg
, &ptr
, 0);
488 if ((args
.journal_tbuffer_size
== 0 ||
489 ((uint32_t) args
.journal_tbuffer_size
) == UINT_MAX
) && errno
!= 0) {
490 fprintf(stderr
, "%s: Invalid tbuffer size %s\n", argv
[0], optarg
);
494 args
.journal_tbuffer_size
*= 1024;
495 else if (*ptr
== 'm')
496 args
.journal_tbuffer_size
*= 1024*1024;
498 if (args
.flags
== VNOVAL
){
501 args
.flags
|= HFSFSMNT_EXTENDED_ARGS
;
505 /* disable the journal */
506 if(args
.flags
== VNOVAL
){
509 args
.flags
|= HFSFSMNT_EXTENDED_ARGS
;
510 args
.journal_disable
= 1;
513 // XXXdbg JOURNAL_NO_GROUP_COMMIT == 0x0001
514 args
.journal_flags
= 0x0001;
517 if (args
.flags
== VNOVAL
)
519 args
.flags
|= HFSFSMNT_NOXONFILES
;
522 args
.hfs_uid
= a_uid(optarg
);
525 args
.hfs_gid
= a_gid(optarg
);
528 args
.hfs_mask
= a_mask(optarg
);
531 encp
= a_encoding(optarg
);
536 getmntopts(optarg
, mopts
, &mntflags
, &dummy
);
540 if (args
.flags
== VNOVAL
)
542 args
.flags
|= HFSFSMNT_WRAPPER
;
543 wrapper_requested
= 1;
550 printf("mount_hfs: ERROR: unrecognized ch = '%c'\n", ch
);
556 if ((mntflags
& MNT_IGNORE_OWNERSHIP
) && !(mntflags
& MNT_UPDATE
)) {
558 * The defaults to be supplied in lieu of the on-disk permissions
559 * (could be overridden by explicit -u, -g, or -m options):
561 if (args
.hfs_uid
== (uid_t
)VNOVAL
) args
.hfs_uid
= UNKNOWNUID
;
562 if (args
.hfs_gid
== (gid_t
)VNOVAL
) args
.hfs_gid
= UNKNOWNGID
;
563 #if OVERRIDE_UNKNOWN_PERMISSIONS
564 if (args
.hfs_mask
== (mode_t
)VNOVAL
) args
.hfs_mask
= ACCESSPERMS
; /* 0777 */
572 printf("mount_hfs: ERROR: argc == %d != 2\n", argc
);
579 if (realpath(argv
[1], dir
) == NULL
)
580 err(1, "realpath %s", dir
);
584 /* HFS volumes need timezone info to convert local to GMT */
585 (void) gettimeofday( &dummy_timeval
, &args
.hfs_timezone
);
587 /* load requested encoding (if any) for hfs volume */
589 if (load_encoding(encp
) != 0)
590 exit(1); /* load failure */
591 args
.hfs_encoding
= encp
->encoding_id
;
595 * For a new mount (non-update case) fill in default values for all args
597 if ((mntflags
& MNT_UPDATE
) == 0) {
601 if (args
.flags
== VNOVAL
)
604 if ((args
.hfs_encoding
== (u_int32_t
)VNOVAL
) && (encp
== NULL
)) {
607 /* Find a suitable encoding preference. */
608 if ((encoding
= get_encoding_pref(dev
)) != -1) {
610 * Note: the encoding kext was loaded by
611 * hfs.util during the file system probe.
613 args
.hfs_encoding
= encoding
;
615 args
.hfs_encoding
= 0;
618 /* when the mountpoint is root, use default values */
619 if (strcmp(dir
, "/") == 0) {
624 /* otherwise inherit from the mountpoint */
625 } else if (stat(dir
, &sb
) == -1)
626 err(1, "stat %s", dir
);
628 if (args
.hfs_uid
== (uid_t
)VNOVAL
)
629 args
.hfs_uid
= sb
.st_uid
;
631 if (args
.hfs_gid
== (gid_t
)VNOVAL
)
632 args
.hfs_gid
= sb
.st_gid
;
634 if (args
.hfs_mask
== (mode_t
)VNOVAL
)
635 args
.hfs_mask
= sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
);
639 printf("mount_hfs: calling mount: \n" );
640 printf("\tdevice = %s\n", dev
);
641 printf("\tmount point = %s\n", dir
);
642 printf("\tmount flags = 0x%08x\n", mntflags
);
643 printf("\targ flags = 0x%x\n", args
.flags
);
644 printf("\tuid = %d\n", args
.hfs_uid
);
645 printf("\tgid = %d\n", args
.hfs_gid
);
646 printf("\tmode = %o\n", args
.hfs_mask
);
647 printf("\tencoding = %ld\n", args
.hfs_encoding
);
651 #if !TARGET_OS_EMBEDDED
653 * We shouldn't really be calling up to other layers, but
654 * an exception was made in this case to fix the situation
655 * where HFS was writable on optical media.
658 if ((_optical_is_writable(dev
) & _OPTICAL_WRITABLE_PACKET
)) {
659 mntflags
|= MNT_RDONLY
;
664 mntflags
|= MNT_RDONLY
;
666 if ((mntflags
& MNT_RDONLY
) == 0) {
668 * get the volume's create date so we can synchronize
669 * it with the root directory create date
671 localCreateTime
= getVolumeCreateDate(dev
);
677 if ((mountStatus
= mount(HFS_MOUNT_TYPE
, dir
, mntflags
, &args
)) < 0) {
678 printf("mount_hfs: error on mount(): error = %d.\n", mountStatus
);
683 * synchronize the root directory's create date
684 * with the volume's create date
687 syncCreateDate(dir
, localCreateTime
);
699 char *gname
, *orig
= s
;
704 for (gname
= s
; *s
&& isdigit(*s
); ++s
);
710 errx(1, "unknown group id: %s", orig
);
721 char *uname
, *orig
= s
;
726 for (uname
= s
; *s
&& isdigit(*s
); ++s
);
732 errx(1, "unknown user id: %s", orig
);
747 if (*s
>= '0' && *s
<= '7') {
749 rv
= strtol(optarg
, &ep
, 8);
751 if (!done
|| rv
< 0 || *ep
)
752 errx(1, "invalid file mode: %s", s
);
756 struct hfs_mnt_encoding
*
763 struct hfs_mnt_encoding
*p
, *q
, *enclist
;
764 int elements
= sizeof(hfs_mnt_encodinglist
) / sizeof(struct hfs_mnt_encoding
);
767 /* Use a binary search to find an encoding match */
768 p
= hfs_mnt_encodinglist
;
769 q
= p
+ (elements
- 1);
771 enclist
= p
+ ((q
- p
) >> 1); /* divide by 2 */
772 compare
= strcmp(s
, enclist
->encoding_name
);
775 else if (compare
> 0)
781 for (uname
= s
; *s
&& isdigit(*s
); ++s
);
783 if (*s
) goto unknown
;
785 encoding
= atoi(uname
);
786 for (i
=0, enclist
= hfs_mnt_encodinglist
; i
< elements
; i
++, enclist
++) {
787 if (enclist
->encoding_id
== encoding
)
792 errx(1, "unknown encoding: %s", uname
);
798 * Get file system's encoding preference.
801 get_encoding_pref(const char *device
)
803 struct hfs_mnt_encoding
*enclist
;
804 HFSMasterDirectoryBlock
* mdbp
;
809 mdbp
= GetMasterBlock(device
);
813 if (SWAP_BE16(mdbp
->drSigWord
) != kHFSSigWord
||
814 (SWAP_BE16(mdbp
->drEmbedSigWord
) == kHFSPlusSigWord
&& (!wrapper_requested
))) {
820 encoding
= GET_HFS_TEXT_ENCODING(SWAP_BE32(mdbp
->drFndrInfo
[4]));
822 if (encoding
== -1) {
823 encoding
= get_encoding_bias();
824 if (encoding
== 0 || encoding
== -1)
825 encoding
= get_default_encoding();
828 /* Check if this is a supported encoding. */
829 elements
= sizeof(hfs_mnt_encodinglist
) / sizeof(struct hfs_mnt_encoding
);
830 for (i
=0, enclist
= hfs_mnt_encodinglist
; i
< elements
; i
++, enclist
++) {
831 if (enclist
->encoding_id
== encoding
)
839 * Get kernel's encoding bias.
845 size_t buflen
= sizeof(int);
849 if (getvfsbyname("hfs", &vfc
) < 0)
853 mib
[1] = vfc
.vfc_typenum
;
854 mib
[2] = HFS_ENCODINGBIAS
;
856 if (sysctl(mib
, 3, &hint
, &buflen
, NULL
, 0) < 0)
863 #define __kCFUserEncodingFileName ("/.CFUserTextEncoding")
866 get_default_encoding()
868 struct passwd
*passwdp
;
870 if ((passwdp
= getpwuid(0))) { /* root account */
871 char buffer
[MAXPATHLEN
+ 1];
874 strlcpy(buffer
, passwdp
->pw_dir
, sizeof(buffer
));
875 strlcat(buffer
, __kCFUserEncodingFileName
, sizeof(buffer
));
877 if ((fd
= open(buffer
, O_RDONLY
, 0)) > 0) {
880 readSize
= read(fd
, buffer
, MAXPATHLEN
);
881 buffer
[(readSize
< 0 ? 0 : readSize
)] = '\0';
883 return strtol(buffer
, NULL
, 0);
886 return (0); /* Fallback to smRoman */
893 (void)fprintf(stderr
,
894 "usage: mount_hfs [-xw] [-u user] [-g group] [-m mask] [-e encoding] [-t tbuffer-size] [-j] [-c] [-o options] special-device filesystem-node\n");