#ifndef lint
static const char rcsid[] =
"$FreeBSD: src/sbin/mount_msdos/mount_msdos.c,v 1.19 2000/01/08 16:47:55 ache Exp $";
#endif
#include <stdint.h>
#include <mach/machine/boolean.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "../msdosfs.kextproj/msdosfs.kmodproj/msdosfsmount.h"
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <fcntl.h>
#include <mntopts.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFStringEncodingExt.h>
#include <CoreFoundation/CFNumber.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOStorageProtocolCharacteristics.h>
static struct mntopt mopts[] = {
MOPT_STDOPTS,
MOPT_FORCE,
MOPT_ASYNC,
MOPT_SYNC,
MOPT_DEFWRITE,
MOPT_NOATIME,
MOPT_UPDATE,
{ NULL }
};
static gid_t a_gid __P((char *));
static uid_t a_uid __P((char *));
static mode_t a_mask __P((char *));
static void usage __P((void));
static int checkLoadable(void);
static char *progname;
static int load_kmod(void);
static void FindVolumeName(struct msdosfs_args *args);
static void
checkpath(const char *path, char resolved[MAXPATHLEN])
{
struct stat sb;
if (realpath(path, resolved) != NULL && stat(resolved, &sb) == 0) {
if (!S_ISDIR(sb.st_mode))
errx(EX_USAGE, "%s: not a directory", resolved);
} else
errx(EX_USAGE, "%s: %s", resolved, strerror(errno));
}
static void
rmslashes(const char *rrpin, char rrpout[MAXPATHLEN])
{
char *rrpoutstart;
for (rrpoutstart = rrpout; *rrpin != '\0'; *rrpout++ = *rrpin++) {
while (*rrpin == '/' && *(rrpin + 1) == '/')
rrpin++;
}
if (rrpout - rrpoutstart > 1 && *(rrpout - 1) == '/')
*(rrpout - 1) = '\0';
else
*rrpout = '\0';
}
static int disk_default_async(char *disk)
{
io_iterator_t iter;
kern_return_t err;
int result = 0;
err = IOServiceGetMatchingServices(kIOMasterPortDefault,
IOBSDNameMatching(kIOMasterPortDefault, 0, disk), &iter);
if (err == 0)
{
io_object_t obj;
obj = IOIteratorNext(iter);
if (obj)
{
CFBooleanRef removableRef;
CFDictionaryRef protocolCharacteristics;
protocolCharacteristics = IORegistryEntrySearchCFProperty(obj,
kIOServicePlane,
CFSTR(kIOPropertyProtocolCharacteristicsKey),
kCFAllocatorDefault,
kIORegistryIterateRecursively|kIORegistryIterateParents);
if (protocolCharacteristics && CFDictionaryGetTypeID() == CFGetTypeID(protocolCharacteristics))
{
CFStringRef location;
location = CFDictionaryGetValue(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectLocationKey));
if(location && CFStringGetTypeID() == CFGetTypeID(location))
{
if(CFEqual(location, CFSTR(kIOPropertyInternalKey)))
result = 1;
}
}
if (protocolCharacteristics)
CFRelease(protocolCharacteristics);
removableRef = IORegistryEntrySearchCFProperty(obj,
kIOServicePlane,
CFSTR(kIOMediaRemovableKey),
kCFAllocatorDefault,
kIORegistryIterateRecursively|kIORegistryIterateParents);
if (removableRef)
{
if (CFBooleanGetTypeID() == CFGetTypeID(removableRef))
{
if (CFBooleanGetValue(removableRef))
result = 0;
}
CFRelease(removableRef);
}
IOObjectRelease(obj);
}
IOObjectRelease(iter);
}
return result;
}
int
main(argc, argv)
int argc;
char **argv;
{
struct msdosfs_args args;
struct stat sb;
int c, mntflags, set_gid, set_uid, set_mask;
mntoptparse_t mp;
char dev[MAXPATHLEN], mntpath[MAXPATHLEN];
struct timezone local_tz;
char *options = "u:g:m:o:";
mntflags = set_gid = set_uid = set_mask = 0;
(void)memset(&args, '\0', sizeof(args));
args.magic = MSDOSFS_ARGSMAGIC;
progname = argv[0];
while (getopt(argc, argv, options) != -1) {
}
if (optind + 2 != argc)
usage();
rmslashes(argv[optind], dev);
if (!strncmp(dev, "/dev/disk", 9) && disk_default_async(dev+5))
mntflags |= MNT_ASYNC;
optreset = 1;
optind = 1;
while ((c = getopt(argc, argv, options)) != -1) {
switch (c) {
case 'u':
args.uid = a_uid(optarg);
set_uid = 1;
break;
case 'g':
args.gid = a_gid(optarg);
set_gid = 1;
break;
case 'm':
args.mask = a_mask(optarg);
set_mask = 1;
break;
case 'o':
mp = getmntopts(optarg, mopts, &mntflags, (int *)&args.flags);
if (mp == NULL)
err(1, NULL);
freemntopts(mp);
break;
case '?':
default:
usage();
break;
}
}
checkpath(argv[optind + 1], mntpath);
if (!set_gid || !set_uid || !set_mask) {
if (stat(mntpath, &sb) == -1)
err(EX_OSERR, "stat %s", mntpath);
if (!set_uid)
args.uid = sb.st_uid;
if (!set_gid)
args.gid = sb.st_gid;
if (!set_mask)
args.mask = sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
if (!set_gid && !set_uid && !set_mask) {
args.mask = ACCESSPERMS;
mntflags |= MNT_UNKNOWNPERMISSIONS;
}
}
args.fspec = dev;
gettimeofday(NULL, &local_tz);
args.secondsWest = local_tz.tz_minuteswest * 60 -
(local_tz.tz_dsttime ? 3600 : 0);
args.flags |= MSDOSFSMNT_SECONDSWEST;
if ((mntflags & MNT_UPDATE) == 0) {
FindVolumeName(&args);
}
if (checkLoadable())
if (load_kmod())
errx(EX_OSERR, "msdos filesystem is not available");
if (mount("msdos", mntpath, mntflags, &args) < 0)
err(EX_OSERR, "%s on %s", dev, mntpath);
exit (0);
}
gid_t
a_gid(s)
char *s;
{
struct group *gr;
char *gname;
gid_t gid;
if ((gr = getgrnam(s)) != NULL)
gid = gr->gr_gid;
else {
for (gname = s; *s && isdigit(*s); ++s);
if (!*s)
gid = atoi(gname);
else
errx(EX_NOUSER, "unknown group id: %s", gname);
}
return (gid);
}
uid_t
a_uid(s)
char *s;
{
struct passwd *pw;
char *uname;
uid_t uid;
if ((pw = getpwnam(s)) != NULL)
uid = pw->pw_uid;
else {
for (uname = s; *s && isdigit(*s); ++s);
if (!*s)
uid = atoi(uname);
else
errx(EX_NOUSER, "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(EX_USAGE, "invalid file mode: %s", s);
return (rv);
}
void
usage()
{
fprintf(stderr, "%s\n%s\n",
"usage: mount_msdos [-o options] [-u user] [-g group] [-m mask]",
" [-s] [-l] [-9] bdev dir");
exit(EX_USAGE);
}
#define FS_TYPE "msdos"
static int checkLoadable(void)
{
int error;
struct vfsconf vfc;
error = getvfsbyname(FS_TYPE, &vfc);
return error;
}
#define LOAD_COMMAND "/sbin/kextload"
#define MSDOS_MODULE_PATH "/System/Library/Extensions/msdosfs.kext"
static int load_kmod(void)
{
int pid;
int result = -1;
union wait status;
pid = fork();
if (pid == 0) {
result = execl(LOAD_COMMAND, LOAD_COMMAND, MSDOS_MODULE_PATH,NULL);
goto Err_Exit;
}
if (pid == -1) {
result = errno;
goto Err_Exit;
}
if ((wait4(pid, (int *)&status, 0, NULL) == pid) && (WIFEXITED(status))) {
result = status.w_retcode;
}
else {
result = -1;
}
Err_Exit:
return (result);
}
static int
oklabel(const char *src)
{
int c, i;
for (i = 0, c = 0; i <= 11; i++) {
c = (u_char)*src++;
if (c < ' ' + !i || strchr("\"*+,./:;<=>?[\\]|", c))
break;
}
return i && !c;
}
static CFStringEncoding GetDefaultDOSEncoding(void)
{
CFStringEncoding encoding;
struct passwd *passwdp;
int fd;
ssize_t size;
char buffer[MAXPATHLEN + 1];
encoding = kCFStringEncodingMacRoman;
if ((passwdp = getpwuid(getuid()))) {
strlcpy(buffer, passwdp->pw_dir, sizeof(buffer));
strlcat(buffer, "/.CFUserTextEncoding", sizeof(buffer));
if ((fd = open(buffer, O_RDONLY, 0)) > 0) {
size = read(fd, buffer, MAXPATHLEN);
buffer[(size < 0 ? 0 : size)] = '\0';
close(fd);
encoding = strtol(buffer, NULL, 0);
}
}
switch (encoding) {
case kCFStringEncodingMacRoman:
encoding = kCFStringEncodingDOSLatin1;
break;
case kCFStringEncodingMacJapanese:
encoding = kCFStringEncodingDOSJapanese;
break;
case kCFStringEncodingMacChineseTrad:
encoding = kCFStringEncodingDOSChineseTrad;
break;
case kCFStringEncodingMacKorean:
encoding = kCFStringEncodingDOSKorean;
break;
case kCFStringEncodingMacArabic:
encoding = kCFStringEncodingDOSArabic;
break;
case kCFStringEncodingMacHebrew:
encoding = kCFStringEncodingDOSHebrew;
break;
case kCFStringEncodingMacGreek:
encoding = kCFStringEncodingDOSGreek;
break;
case kCFStringEncodingMacCyrillic:
case kCFStringEncodingMacUkrainian:
encoding = kCFStringEncodingDOSCyrillic;
break;
case kCFStringEncodingMacThai:
encoding = kCFStringEncodingDOSThai;
break;
case kCFStringEncodingMacChineseSimp:
encoding = kCFStringEncodingDOSChineseSimplif;
break;
case kCFStringEncodingMacCentralEurRoman:
case kCFStringEncodingMacCroatian:
case kCFStringEncodingMacRomanian:
encoding = kCFStringEncodingDOSLatin2;
break;
case kCFStringEncodingMacTurkish:
encoding = kCFStringEncodingDOSTurkish;
break;
case kCFStringEncodingMacIcelandic:
encoding = kCFStringEncodingDOSIcelandic;
break;
case kCFStringEncodingMacFarsi:
encoding = kCFStringEncodingDOSArabic;
break;
default:
encoding = kCFStringEncodingInvalidId;
break;
}
return encoding;
}
#define MAX_DOS_BLOCKSIZE 4096
struct dosdirentry {
u_int8_t name[11];
u_int8_t attr;
u_int8_t reserved;
u_int8_t createTimeTenth;
u_int16_t createTime;
u_int16_t createDate;
u_int16_t accessDate;
u_int16_t clusterHi;
u_int16_t modTime;
u_int16_t modDate;
u_int16_t clusterLo;
u_int32_t size;
};
#define ATTR_VOLUME_NAME 0x08
#define ATTR_VOLUME_MASK 0x18
#define ATTR_LONG_NAME 0x0F
#define ATTR_MASK 0x3F
#define SLOT_EMPTY 0x00
#define SLOT_DELETED 0xE5U
#define SLOT_E5 0x05
#define CLUST_FIRST 2
#define CLUST_RESERVED 0x0FFFFFF7
static void FindVolumeName(struct msdosfs_args *args)
{
int fd;
u_int32_t i;
struct dosdirentry *dir;
void *rootBuffer;
unsigned bytesPerSector;
unsigned sectorsPerCluster;
unsigned rootDirEntries;
unsigned reservedSectors;
unsigned numFATs;
u_int32_t sectorsPerFAT;
off_t readOffset;
ssize_t readAmount;
unsigned char buf[MAX_DOS_BLOCKSIZE];
char label[12];
CFStringRef cfstr;
bzero(label, sizeof(label));
rootBuffer = NULL;
fd = open(args->fspec, O_RDONLY, 0);
if (fd<0)
err(EX_OSERR, "%s", args->fspec);
if (pread(fd, buf, MAX_DOS_BLOCKSIZE, 0) != MAX_DOS_BLOCKSIZE)
err(EX_OSERR, "%s", args->fspec);
bytesPerSector = buf[11] + buf[12]*256;
if (bytesPerSector < 512 || bytesPerSector > MAX_DOS_BLOCKSIZE || (bytesPerSector & (bytesPerSector-1)))
errx(EX_OSERR, "Unsupported sector size (%u)", bytesPerSector);
sectorsPerCluster = buf[13];
if (sectorsPerCluster==0 || (sectorsPerCluster & (sectorsPerCluster-1)))
errx(EX_OSERR, "Unsupported sectors per cluster (%u)", sectorsPerCluster);
reservedSectors = buf[14] + buf[15]*256;
numFATs = buf[16];
rootDirEntries = buf[17] + buf[18]*256;
if (rootDirEntries == 0) {
bcopy(&buf[71], label, 11);
} else {
if (buf[38] == 0x29)
bcopy(&buf[43], label, 11);
}
if (rootDirEntries != 0) {
u_int32_t firstRootSector;
sectorsPerFAT = buf[22] + buf[23]*256;
firstRootSector = reservedSectors + numFATs * sectorsPerFAT;
readOffset = firstRootSector * bytesPerSector;
readAmount = (rootDirEntries * sizeof(struct dosdirentry) + bytesPerSector-1) / bytesPerSector;
readAmount *= bytesPerSector;
rootBuffer = malloc(readAmount);
if (rootBuffer == NULL)
errx(EX_OSERR, "Out of memory");
if (pread(fd, rootBuffer, readAmount, readOffset) != readAmount)
err(EX_OSERR, "%s", args->fspec);
for (i=0,dir=rootBuffer; i<rootDirEntries; ++i,++dir) {
if (dir->name[0] == SLOT_EMPTY)
goto end_of_dir;
if (dir->name[0] == SLOT_DELETED)
continue;
if ((dir->attr & ATTR_MASK) == ATTR_LONG_NAME)
continue;
if ((dir->attr & ATTR_VOLUME_MASK) == ATTR_VOLUME_NAME) {
bcopy(dir->name, label, 11);
goto end_of_dir;
}
}
} else {
u_int32_t cluster;
u_int32_t clusterOffset;
sectorsPerFAT = buf[36] + (buf[37]<<8L) + (buf[38]<<16L) + (buf[39]<<24L);
clusterOffset = reservedSectors + numFATs * sectorsPerFAT;
readAmount = bytesPerSector * sectorsPerCluster;
rootBuffer = malloc(readAmount);
if (rootBuffer == NULL)
errx(EX_OSERR, "Out of memory");
rootDirEntries = readAmount / sizeof(struct dosdirentry);
cluster = buf[44] + (buf[45]<<8L) + (buf[46]<<16L) + (buf[47]<<24L);
while (cluster >= CLUST_FIRST && cluster < CLUST_RESERVED) {
readOffset = (cluster - CLUST_FIRST) * sectorsPerCluster + clusterOffset;
readOffset *= bytesPerSector;
if (pread(fd, rootBuffer, readAmount, readOffset) != readAmount)
err(EX_OSERR, "%s", args->fspec);
for (i=0,dir=rootBuffer; i<rootDirEntries; ++i,++dir) {
if (dir->name[0] == SLOT_EMPTY)
goto end_of_dir;
if (dir->name[0] == SLOT_DELETED)
continue;
if ((dir->attr & ATTR_MASK) == ATTR_LONG_NAME)
continue;
if ((dir->attr & ATTR_VOLUME_MASK) == ATTR_VOLUME_NAME) {
bcopy(dir->name, label, 11);
goto end_of_dir;
}
}
readOffset = reservedSectors + ((cluster * 4) / bytesPerSector);
readOffset *= bytesPerSector;
if (pread(fd, buf, bytesPerSector, readOffset) != bytesPerSector)
err(EX_OSERR, "%s", args->fspec);
i = (cluster * 4) % bytesPerSector;
cluster = buf[i] + (buf[i+1]<<8L) + (buf[i+2]<<16L) + (buf[i+3]<<24L);
cluster &= 0x0FFFFFFF;
}
}
end_of_dir:
if (rootBuffer)
free(rootBuffer);
close(fd);
if (label[0] == 0x05)
label[0] = 0xE5;
if (!oklabel(label))
label[0] = 0;
i = 11;
do {
--i;
if (label[i] == ' ')
label[i] = 0;
else
break;
} while (i != 0);
cfstr = CFStringCreateWithCString(NULL, label, GetDefaultDOSEncoding());
if (cfstr == NULL)
cfstr = CFStringCreateWithCString(NULL, label, kCFStringEncodingDOSLatin1);
if (cfstr == NULL)
args->label[0] = 0;
else {
CFMutableStringRef mutable;
mutable = CFStringCreateMutableCopy(NULL, 0, cfstr);
if (mutable != NULL) {
CFStringNormalize(mutable, kCFStringNormalizationFormD);
CFStringGetCString(mutable, (char *)args->label, sizeof(args->label), kCFStringEncodingUTF8);
CFRelease(mutable);
}
CFRelease(cfstr);
}
args->flags |= MSDOSFSMNT_LABEL;
}