/* * Copyright (c) 1999-2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Sensible wrappers over the byte-swapping routines */ #include "hfs_endian.h" #include "../disklib/mntopts.h" /* This really belongs in mntopts.h */ #define MOPT_PERMISSIONS { "perm", 1, MNT_UNKNOWNPERMISSIONS, 0 } struct mntopt mopts[] = { MOPT_STDOPTS, MOPT_PERMISSIONS, MOPT_UPDATE, { NULL } }; #define HFS_MOUNT_TYPE "hfs" #define DEFAULT_ROOTUID -2 #define DEFAULT_ANON_UID -2 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 *)); struct hfs_mnt_encoding * get_encoding_pref __P((char *)); int get_encoding_bias __P((void)); unsigned int get_default_encoding(void); void usage __P((void)); typedef struct CreateDateAttrBuf { u_long size; struct timespec creationTime; } CreateDateAttrBuf; #define HFS_BLOCK_SIZE 512 /* * This is the straight GMT conversion constant: * 00:00:00 January 1, 1970 - 00:00:00 January 1, 1904 * (3600 * 24 * ((365 * (1970 - 1904)) + (((1970 - 1904) / 4) + 1))) */ #define MAC_GMT_FACTOR 2082844800UL #define KEXT_LOAD_COMMAND "/sbin/kextload" #define ENCODING_MODULE_PATH "/System/Library/Filesystems/hfs.fs/Encodings/" #define MXENCDNAMELEN 16 /* Maximun length of encoding name string */ struct hfs_mnt_encoding { char encoding_name[MXENCDNAMELEN]; /* encoding type name */ u_long encoding_id; /* encoding type number */ }; /* * Lookup table for hfs encoding names * Note: Names must be in alphabetical order */ 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 }, /* default */ { "Romanian", 38 }, { "Sinhalese", 18 }, { "Tamil", 14 }, { "Telugu", 15 }, { "Thai", 21 }, { "Tibetan", 26 }, { "Turkish", 35 }, { "Ukrainian", 152 }, { "Vietnamese", 30 }, }; u_long getVolumeCreateDate(const char *device) { int fd = 0; off_t offset; char * bufPtr; HFSMasterDirectoryBlock * mdbPtr; u_long volume_create_time = 0; bufPtr = (char *)malloc(HFS_BLOCK_SIZE); if ( ! bufPtr ) goto exit; fd = open( device, O_RDONLY | O_NDELAY, 0 ); if( fd <= 0 ) goto exit; offset = (off_t)(2 * HFS_BLOCK_SIZE); if (lseek(fd, offset, SEEK_SET) != offset) goto exit; if (read(fd, bufPtr, HFS_BLOCK_SIZE) != HFS_BLOCK_SIZE) goto exit; mdbPtr = (HFSMasterDirectoryBlock *) bufPtr; /* get the create date from the MDB (embedded case) or Volume Header */ if ((mdbPtr->drSigWord == SWAP_BE16 (kHFSSigWord)) && (mdbPtr->drEmbedSigWord == SWAP_BE16 (kHFSPlusSigWord))) { /* Embedded volume*/ volume_create_time = SWAP_BE32 (mdbPtr->drCrDate); } else if (mdbPtr->drSigWord == kHFSPlusSigWord ) { HFSPlusVolumeHeader * volHdrPtr = (HFSPlusVolumeHeader *) bufPtr; volume_create_time = SWAP_BE32 (volHdrPtr->createDate); } else { goto exit; /* cound not match signature */ } if (volume_create_time > MAC_GMT_FACTOR) volume_create_time -= MAC_GMT_FACTOR; else volume_create_time = 0; /* don't let date go negative! */ exit: if ( fd > 0 ) close( fd ); if ( bufPtr ) free( bufPtr ); return volume_create_time; } void syncCreateDate(const char *mntpt, u_long 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 the root directory's create date doesn't match * and its within +/- 15 seconds, then update it */ if ((newCreateTime != attrReturnBuffer.creationTime.tv_sec) && (( newCreateTime - attrReturnBuffer.creationTime.tv_sec) > -15) && ((newCreateTime - attrReturnBuffer.creationTime.tv_sec) < 15)) { attrReturnBuffer.creationTime.tv_sec = (u_long) newCreateTime; (void) setattrlist (path, &attributes, &attrReturnBuffer.creationTime, sizeof(attrReturnBuffer.creationTime), 0); } } /* * load_encoding * loads an hfs encoding converter module into the kernel * * Note: unloading of encoding converter modules is done in the kernel */ static void load_encoding(struct hfs_mnt_encoding *encp) { int pid; int loaded; union wait status; struct stat sb; char kmodfile[MAXPATHLEN]; /* MacRoman encoding (0) is built into the kernel */ if (encp->encoding_id == 0) return; sprintf(kmodfile, "%sHFS_Mac%s.kext", ENCODING_MODULE_PATH, encp->encoding_name); if (stat(kmodfile, &sb) == -1) errx(1, "unable to find: %s", kmodfile); loaded = 0; pid = fork(); if (pid == 0) { (void) execl(KEXT_LOAD_COMMAND, KEXT_LOAD_COMMAND, kmodfile, NULL); exit(1); /* We can only get here if the exec failed */ } else if (pid != -1) { if ((waitpid(pid, (int *)&status, 0) == pid) && WIFEXITED(status)) { /* we attempted a load */ loaded = 1; } } if (!loaded) errx(1, "unable to load: %s", kmodfile); } 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; /* gettimeofday() crashes if the first argument is NULL */ u_long localCreateTime; struct hfs_mnt_encoding *encp; mntflags = 0; encp = NULL; (void)memset(&args, '\0', sizeof(struct hfs_mount_args)); /* * For a mount update, the following args must be explictly * passed in as options to change their value. On a new * mount, default values will be computed for all 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_long)VNOVAL; optind = optreset = 1; /* Reset for parse of new argv. */ 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 (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 = HFSFSMNT_EXTENDED_ARGS; break; } case 'j': args.journal_disable = 1; break; case 'c': // XXXdbg JOURNAL_NO_GROUP_COMMIT == 0x0001 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_UNKNOWNPERMISSIONS) { /* The defaults to be supplied in lieu of the on-disk permissions (could be overridden by explicit -u, -g, or -m options): */ 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; /* 0777 */ #endif }; } break; case 'w': if (args.flags == VNOVAL) args.flags = 0; args.flags |= HFSFSMNT_WRAPPER; break; case '?': usage(); break; default: #if DEBUG printf("mount_hfs: ERROR: unrecognized ch = '%c'\n", ch); #endif usage(); }; /* switch */ 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; args.export.ex_root = DEFAULT_ROOTUID; args.export.ex_anon.cr_uid = DEFAULT_ANON_UID; /* mapping for anonymous users */ if (mntflags & MNT_RDONLY) args.export.ex_flags = MNT_EXRDONLY; else args.export.ex_flags = 0; /* HFS volumes need timezone info to convert local to GMT */ (void) gettimeofday( &dummy_timeval, &args.hfs_timezone ); /* load requested encoding (if any) for hfs volume */ if (encp != NULL) { load_encoding(encp); args.hfs_encoding = encp->encoding_id; } /* * For a new mount (non-update case) fill in default values for all args */ if ((mntflags & MNT_UPDATE) == 0) { struct stat sb; if (args.flags == VNOVAL) args.flags = 0; if ((args.hfs_encoding == (u_long)VNOVAL) && (encp == NULL)) { args.hfs_encoding = 0; /* Check if volume had a previous encoding preference. */ encp = get_encoding_pref(dev); if (encp != NULL) { load_encoding(encp); args.hfs_encoding = encp->encoding_id; } } /* when the mountpoint is root, use default values */ if (strcmp(dir, "/") == 0) { sb.st_mode = 0777; sb.st_uid = 0; sb.st_gid = 0; /* otherwise inherit from the mountpoint */ } 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 ((mntflags & MNT_RDONLY) == 0) { /* * get the volume's create date so we can synchronize * it with the root directory create date */ 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); }; /* * synchronize the root directory's create date * with the volume's create date */ if (localCreateTime) syncCreateDate(dir, localCreateTime); exit(0); } gid_t a_gid(s) char *s; { struct group *gr; char *gname; gid_t gid = 0; if ((gr = getgrnam(s)) != NULL) gid = gr->gr_gid; else { for (gname = s; *s && isdigit(*s); ++s); if (!*s) gid = atoi(gname); else errx(1, "unknown group id: %s", gname); } return (gid); } uid_t a_uid(s) char *s; { struct passwd *pw; char *uname; uid_t uid = 0; if ((pw = getpwnam(s)) != NULL) uid = pw->pw_uid; else { for (uname = s; *s && isdigit(*s); ++s); if (!*s) uid = atoi(uname); else errx(1, "unknown user id: %s", uname); } 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_long encoding; struct hfs_mnt_encoding *p, *q, *enclist; int elements = sizeof(hfs_mnt_encodinglist) / sizeof(struct hfs_mnt_encoding); int compare; /* Use a binary search to find an encoding match */ p = hfs_mnt_encodinglist; q = p + (elements - 1); while (p <= q) { enclist = p + ((q - p) >> 1); /* divide by 2 */ 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); } /* * Get file system's encoding preference. */ struct hfs_mnt_encoding * get_encoding_pref(char *dev) { char buffer[HFS_BLOCK_SIZE]; struct hfs_mnt_encoding *enclist; HFSMasterDirectoryBlock * mdbp; int encoding = -1; int elements; int fd; int i; /* Can only load encoding modules if root. */ if (geteuid() != 0) goto next; fd = open(dev, O_RDONLY | O_NDELAY, 0); if (fd == -1) goto next; if (pread(fd, buffer, sizeof(buffer), 1024) != sizeof(buffer)) { close(fd); goto next; } mdbp = (HFSMasterDirectoryBlock *) buffer; if (SWAP_BE16(mdbp->drSigWord) == kHFSSigWord) { encoding = GET_HFS_TEXT_ENCODING(SWAP_BE32(mdbp->drFndrInfo[4])); } close(fd); next: 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 (enclist); } return (NULL); } /* * Get kernel's encoding bias. */ 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))) { /* root account */ char buffer[MAXPATHLEN + 1]; int fd; strcpy(buffer, passwdp->pw_dir); strcat(buffer, __kCFUserEncodingFileName); 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); /* Fallback to smRoman */ } 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); }