#include <sys/types.h>
#include <sys/attr.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/vnode.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/disk.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <hfs/hfs_mount.h>
#include <hfs/hfs_format.h>
#include <TargetConditionals.h>
#include "hfs_endian.h"
#if !TARGET_OS_EMBEDDED
#include "optical.h"
#endif
#include <mntopts.h>
struct mntopt mopts[] = {
MOPT_STDOPTS,
MOPT_IGNORE_OWNERSHIP,
MOPT_PERMISSIONS,
MOPT_UPDATE,
{ NULL }
};
#define HFS_MOUNT_TYPE "hfs"
gid_t a_gid __P((char *));
uid_t a_uid __P((char *));
mode_t a_mask __P((char *));
struct hfs_mnt_encoding * a_encoding __P((char *));
int get_encoding_pref __P((const char *));
int get_encoding_bias __P((void));
unsigned int get_default_encoding(void);
void usage __P((void));
int is_hfs_std = 0;
int wrapper_requested = 0;
typedef struct CreateDateAttrBuf {
u_int32_t size;
struct timespec creationTime;
} CreateDateAttrBuf;
#define HFS_BLOCK_SIZE 512
#define MAC_GMT_FACTOR 2082844800UL
#define KEXT_LOAD_COMMAND "/sbin/kextload"
#define ENCODING_MODULE_PATH "/System/Library/Filesystems/hfs.fs/Encodings/"
#define MXENCDNAMELEN 16
struct hfs_mnt_encoding {
char encoding_name[MXENCDNAMELEN];
u_int32_t encoding_id;
};
struct hfs_mnt_encoding hfs_mnt_encodinglist[] = {
{ "Arabic", 4 },
{ "Armenian", 24 },
{ "Bengali", 13 },
{ "Burmese", 19 },
{ "Celtic", 39 },
{ "CentralEurRoman", 29 },
{ "ChineseSimp", 25 },
{ "ChineseTrad", 2 },
{ "Croatian", 36 },
{ "Cyrillic", 7 },
{ "Devanagari", 9 },
{ "Ethiopic", 28 },
{ "Farsi", 140 },
{ "Gaelic", 40 },
{ "Georgian", 23 },
{ "Greek", 6 },
{ "Gujarati", 11 },
{ "Gurmukhi", 10 },
{ "Hebrew", 5 },
{ "Icelandic", 37 },
{ "Japanese", 1 },
{ "Kannada", 16 },
{ "Khmer", 20 },
{ "Korean", 3 },
{ "Laotian", 22 },
{ "Malayalam", 17 },
{ "Mongolian", 27 },
{ "Oriya", 12 },
{ "Roman", 0 },
{ "Romanian", 38 },
{ "Sinhalese", 18 },
{ "Tamil", 14 },
{ "Telugu", 15 },
{ "Thai", 21 },
{ "Tibetan", 26 },
{ "Turkish", 35 },
{ "Ukrainian", 152 },
{ "Vietnamese", 30 },
};
const char *rawdevice(const char *path)
{
const char *devdisk = "/dev/disk";
static char raw[MAXPATHLEN];
if (!strncmp(path, devdisk, strlen(devdisk))) {
if (snprintf(raw, sizeof(raw), "/dev/r%s", path+5) < sizeof(raw)) {
return raw;
}
}
return path;
}
void *GetMasterBlock(const char *device)
{
static char *masterBlock = NULL;
char *buf = NULL;
int err;
int fd = -1;
uint32_t blockSize;
ssize_t amount;
off_t offset;
if (masterBlock != NULL) {
return masterBlock;
}
device = rawdevice(device);
fd = open(device, O_RDONLY | O_NDELAY, 0);
if (fd < 0) {
fprintf(stderr, "GetMasterBlock: Error %d opening %s\n", errno, device);
goto done;
}
err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize);
if (err == -1) {
fprintf(stderr, "GetMasterBlock: Error %d getting block size\n", errno);
goto done;
}
offset = blockSize > 1024 ? 0 : 1024;
buf = malloc(blockSize);
if (buf == NULL) {
fprintf(stderr, "GetMasterBlock: Could not malloc %u bytes\n", blockSize);
goto done;
}
amount = pread(fd, buf, blockSize, offset);
if (amount != blockSize) {
fprintf(stderr, "GetMasterBlock: Error %d from read; amount=%ld, wanted=%u\n", errno, amount, blockSize);
goto done;
}
masterBlock = buf + 1024 - offset;
buf = NULL;
done:
if (fd >= 0)
close(fd);
if (buf != NULL)
free(buf);
return masterBlock;
}
u_int32_t getVolumeCreateDate(const char *device)
{
HFSMasterDirectoryBlock * mdbPtr;
u_int32_t volume_create_time = 0;
mdbPtr = GetMasterBlock(device);
if (mdbPtr == NULL) goto exit;
if ((mdbPtr->drSigWord == SWAP_BE16 (kHFSSigWord)) &&
(mdbPtr->drEmbedSigWord == SWAP_BE16 (kHFSPlusSigWord))) {
volume_create_time = SWAP_BE32 (mdbPtr->drCrDate);
} else if (mdbPtr->drSigWord == kHFSPlusSigWord ) {
HFSPlusVolumeHeader * volHdrPtr = (HFSPlusVolumeHeader *) mdbPtr;
volume_create_time = SWAP_BE32 (volHdrPtr->createDate);
} else {
goto exit;
}
if (volume_create_time > MAC_GMT_FACTOR)
volume_create_time -= MAC_GMT_FACTOR;
else
volume_create_time = 0;
exit:
return volume_create_time;
}
void syncCreateDate(const char *mntpt, u_int32_t localCreateTime)
{
int result;
char path[256];
struct attrlist attributes;
CreateDateAttrBuf attrReturnBuffer;
int64_t gmtCreateTime;
int32_t gmtOffset;
int32_t newCreateTime;
snprintf(path, sizeof(path), "%s/", mntpt);
attributes.bitmapcount = ATTR_BIT_MAP_COUNT;
attributes.reserved = 0;
attributes.commonattr = ATTR_CMN_CRTIME;
attributes.volattr = 0;
attributes.dirattr = 0;
attributes.fileattr = 0;
attributes.forkattr = 0;
result = getattrlist(path, &attributes, &attrReturnBuffer, sizeof(attrReturnBuffer), 0 );
if (result) return;
gmtCreateTime = attrReturnBuffer.creationTime.tv_sec;
gmtOffset = gmtCreateTime - (int64_t) localCreateTime + 900;
if (gmtOffset > 0) {
gmtOffset = 1800 * (gmtOffset / 1800);
} else {
gmtOffset = -1800 * ((-gmtOffset + 1799) / 1800);
}
newCreateTime = localCreateTime + gmtOffset;
if ((newCreateTime != attrReturnBuffer.creationTime.tv_sec) &&
(( newCreateTime - attrReturnBuffer.creationTime.tv_sec) > -15) &&
((newCreateTime - attrReturnBuffer.creationTime.tv_sec) < 15)) {
attrReturnBuffer.creationTime.tv_sec = (time_t) newCreateTime;
(void) setattrlist (path,
&attributes,
&attrReturnBuffer.creationTime,
sizeof(attrReturnBuffer.creationTime),
0);
}
}
static int
load_encoding(struct hfs_mnt_encoding *encp)
{
int pid;
int loaded;
union wait status;
struct stat sb;
char kmodfile[MAXPATHLEN];
if (encp->encoding_id == 0)
return (0);
snprintf(kmodfile, sizeof(kmodfile), "%sHFS_Mac%s.kext", ENCODING_MODULE_PATH, encp->encoding_name);
if (stat(kmodfile, &sb) == -1) {
fprintf(stdout, "unable to find: %s\n", kmodfile);
return (-1);
}
loaded = 0;
pid = fork();
if (pid == 0) {
(void) execl(KEXT_LOAD_COMMAND, KEXT_LOAD_COMMAND, kmodfile, NULL);
exit(1);
} else if (pid != -1) {
if ((waitpid(pid, (int *)&status, 0) == pid) && WIFEXITED(status)) {
loaded = 1;
}
}
if (!loaded) {
fprintf(stderr, "unable to load: %s\n", kmodfile);
return (-1);
}
return (0);
}
int
main(argc, argv)
int argc;
char **argv;
{
struct hfs_mount_args args;
int ch, mntflags;
char *dev, dir[MAXPATHLEN];
int mountStatus;
struct timeval dummy_timeval;
u_int32_t localCreateTime;
struct hfs_mnt_encoding *encp;
#if TARGET_OS_EMBEDDED
mntflags = MNT_NOATIME;
#else
mntflags = 0;
#endif
encp = NULL;
(void)memset(&args, '\0', sizeof(struct hfs_mount_args));
args.flags = VNOVAL;
args.hfs_uid = (uid_t)VNOVAL;
args.hfs_gid = (gid_t)VNOVAL;
args.hfs_mask = (mode_t)VNOVAL;
args.hfs_encoding = (u_int32_t)VNOVAL;
optind = optreset = 1;
while ((ch = getopt(argc, argv, "xu:g:m:e:o:wt:jc")) != EOF)
switch (ch) {
case 't': {
char *ptr;
args.journal_tbuffer_size = strtoul(optarg, &ptr, 0);
if ((args.journal_tbuffer_size == 0 || args.journal_tbuffer_size == ULONG_MAX) && errno != 0) {
fprintf(stderr, "%s: Invalid tbuffer size %s\n", argv[0], optarg);
exit(5);
} else {
if (*ptr == 'k')
args.journal_tbuffer_size *= 1024;
else if (*ptr == 'm')
args.journal_tbuffer_size *= 1024*1024;
}
if (args.flags == VNOVAL){
args.flags = 0;
}
args.flags |= HFSFSMNT_EXTENDED_ARGS;
break;
}
case 'j':
if(args.flags == VNOVAL){
args.flags = 0;
}
args.flags |= HFSFSMNT_EXTENDED_ARGS;
args.journal_disable = 1;
break;
case 'c':
args.journal_flags = 0x0001;
break;
case 'x':
if (args.flags == VNOVAL)
args.flags = 0;
args.flags |= HFSFSMNT_NOXONFILES;
break;
case 'u':
args.hfs_uid = a_uid(optarg);
break;
case 'g':
args.hfs_gid = a_gid(optarg);
break;
case 'm':
args.hfs_mask = a_mask(optarg);
break;
case 'e':
encp = a_encoding(optarg);
break;
case 'o':
{
int dummy;
getmntopts(optarg, mopts, &mntflags, &dummy);
if (mntflags & MNT_IGNORE_OWNERSHIP) {
if (args.hfs_uid == (uid_t)VNOVAL) args.hfs_uid = UNKNOWNUID;
if (args.hfs_gid == (gid_t)VNOVAL) args.hfs_gid = UNKNOWNGID;
#if OVERRIDE_UNKNOWN_PERMISSIONS
if (args.hfs_mask == (mode_t)VNOVAL) args.hfs_mask = ACCESSPERMS;
#endif
};
}
break;
case 'w':
if (args.flags == VNOVAL)
args.flags = 0;
args.flags |= HFSFSMNT_WRAPPER;
wrapper_requested = 1;
break;
case '?':
usage();
break;
default:
#if DEBUG
printf("mount_hfs: ERROR: unrecognized ch = '%c'\n", ch);
#endif
usage();
};
argc -= optind;
argv += optind;
if (argc != 2) {
#if DEBUG
printf("mount_hfs: ERROR: argc == %d != 2\n", argc);
#endif
usage();
}
dev = argv[0];
if (realpath(argv[1], dir) == NULL)
err(1, "realpath %s", dir);
args.fspec = dev;
(void) gettimeofday( &dummy_timeval, &args.hfs_timezone );
if (encp != NULL) {
if (load_encoding(encp) != 0)
exit(1);
args.hfs_encoding = encp->encoding_id;
}
if ((mntflags & MNT_UPDATE) == 0) {
struct stat sb;
if (args.flags == VNOVAL)
args.flags = 0;
if ((args.hfs_encoding == (u_int32_t)VNOVAL) && (encp == NULL)) {
int encoding;
if ((encoding = get_encoding_pref(dev)) != -1) {
args.hfs_encoding = encoding;
} else {
args.hfs_encoding = 0;
}
}
if (strcmp(dir, "/") == 0) {
sb.st_mode = 0777;
sb.st_uid = 0;
sb.st_gid = 0;
} else if (stat(dir, &sb) == -1)
err(1, "stat %s", dir);
if (args.hfs_uid == (uid_t)VNOVAL)
args.hfs_uid = sb.st_uid;
if (args.hfs_gid == (gid_t)VNOVAL)
args.hfs_gid = sb.st_gid;
if (args.hfs_mask == (mode_t)VNOVAL)
args.hfs_mask = sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
}
#if DEBUG
printf("mount_hfs: calling mount: \n" );
printf("\tdevice = %s\n", dev);
printf("\tmount point = %s\n", dir);
printf("\tmount flags = 0x%08x\n", mntflags);
printf("\targ flags = 0x%x\n", args.flags);
printf("\tuid = %d\n", args.hfs_uid);
printf("\tgid = %d\n", args.hfs_gid);
printf("\tmode = %o\n", args.hfs_mask);
printf("\tencoding = %ld\n", args.hfs_encoding);
#endif
#if !TARGET_OS_EMBEDDED
if ((_optical_is_writable(dev) & _OPTICAL_WRITABLE_PACKET)) {
mntflags |= MNT_RDONLY;
}
#endif
if (is_hfs_std)
mntflags |= MNT_RDONLY;
if ((mntflags & MNT_RDONLY) == 0) {
localCreateTime = getVolumeCreateDate(dev);
}
else {
localCreateTime = 0;
}
if ((mountStatus = mount(HFS_MOUNT_TYPE, dir, mntflags, &args)) < 0) {
#if DEBUG
printf("mount_hfs: error on mount(): error = %d.\n", mountStatus);
#endif
err(1, NULL);
};
if (localCreateTime)
syncCreateDate(dir, localCreateTime);
exit(0);
}
gid_t
a_gid(s)
char *s;
{
struct group *gr;
char *gname, *orig = s;
gid_t gid = 0;
if (*s == '-')
s++;
for (gname = s; *s && isdigit(*s); ++s);
if (!*s) {
gid = atoi(gname);
} else {
gr = getgrnam(orig);
if (gr == NULL)
errx(1, "unknown group id: %s", orig);
gid = gr->gr_gid;
}
return (gid);
}
uid_t
a_uid(s)
char *s;
{
struct passwd *pw;
char *uname, *orig = s;
uid_t uid = 0;
if (*s == '-')
s++;
for (uname = s; *s && isdigit(*s); ++s);
if (!*s) {
uid = atoi(uname);
} else {
pw = getpwnam(orig);
if (pw == NULL)
errx(1, "unknown user id: %s", orig);
uid = pw->pw_uid;
}
return (uid);
}
mode_t
a_mask(s)
char *s;
{
int done, rv;
char *ep;
done = 0;
rv = -1;
if (*s >= '0' && *s <= '7') {
done = 1;
rv = strtol(optarg, &ep, 8);
}
if (!done || rv < 0 || *ep)
errx(1, "invalid file mode: %s", s);
return (rv);
}
struct hfs_mnt_encoding *
a_encoding(s)
char *s;
{
char *uname;
int i;
u_int32_t encoding;
struct hfs_mnt_encoding *p, *q, *enclist;
int elements = sizeof(hfs_mnt_encodinglist) / sizeof(struct hfs_mnt_encoding);
int compare;
p = hfs_mnt_encodinglist;
q = p + (elements - 1);
while (p <= q) {
enclist = p + ((q - p) >> 1);
compare = strcmp(s, enclist->encoding_name);
if (compare < 0)
q = enclist - 1;
else if (compare > 0)
p = enclist + 1;
else
return (enclist);
}
for (uname = s; *s && isdigit(*s); ++s);
if (*s) goto unknown;
encoding = atoi(uname);
for (i=0, enclist = hfs_mnt_encodinglist; i < elements; i++, enclist++) {
if (enclist->encoding_id == encoding)
return (enclist);
}
unknown:
errx(1, "unknown encoding: %s", uname);
return (NULL);
}
int
get_encoding_pref(const char *device)
{
struct hfs_mnt_encoding *enclist;
HFSMasterDirectoryBlock * mdbp;
int encoding = -1;
int elements;
int i;
mdbp = GetMasterBlock(device);
if (mdbp == NULL)
return 0;
if (SWAP_BE16(mdbp->drSigWord) != kHFSSigWord ||
SWAP_BE16(mdbp->drEmbedSigWord) == kHFSPlusSigWord && (!wrapper_requested)) {
return (-1);
}
else {
is_hfs_std = 1;
}
encoding = GET_HFS_TEXT_ENCODING(SWAP_BE32(mdbp->drFndrInfo[4]));
if (encoding == -1) {
encoding = get_encoding_bias();
if (encoding == 0 || encoding == -1)
encoding = get_default_encoding();
}
elements = sizeof(hfs_mnt_encodinglist) / sizeof(struct hfs_mnt_encoding);
for (i=0, enclist = hfs_mnt_encodinglist; i < elements; i++, enclist++) {
if (enclist->encoding_id == encoding)
return (encoding);
}
return (0);
}
int
get_encoding_bias()
{
int mib[3];
size_t buflen = sizeof(int);
struct vfsconf vfc;
int hint = 0;
if (getvfsbyname("hfs", &vfc) < 0)
goto error;
mib[0] = CTL_VFS;
mib[1] = vfc.vfc_typenum;
mib[2] = HFS_ENCODINGBIAS;
if (sysctl(mib, 3, &hint, &buflen, NULL, 0) < 0)
goto error;
return (hint);
error:
return (-1);
}
#define __kCFUserEncodingFileName ("/.CFUserTextEncoding")
unsigned int
get_default_encoding()
{
struct passwd *passwdp;
if ((passwdp = getpwuid(0))) {
char buffer[MAXPATHLEN + 1];
int fd;
strlcpy(buffer, passwdp->pw_dir, sizeof(buffer));
strlcat(buffer, __kCFUserEncodingFileName, sizeof(buffer));
if ((fd = open(buffer, O_RDONLY, 0)) > 0) {
size_t readSize;
readSize = read(fd, buffer, MAXPATHLEN);
buffer[(readSize < 0 ? 0 : readSize)] = '\0';
close(fd);
return strtol(buffer, NULL, 0);
}
}
return (0);
}
void
usage()
{
(void)fprintf(stderr,
"usage: mount_hfs [-xw] [-u user] [-g group] [-m mask] [-e encoding] [-t tbuffer-size] [-j] [-c] [-o options] special-device filesystem-node\n");
(void)fprintf(stderr, " -j disables journaling; -c disables group-commit for journaling\n");
exit(1);
}