Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
MFSLivesUtil.c
/* |
File: MFSLivesUtil.c |
Contains: MFSLives utility; user space support included in the .fs bundle. |
Written by: DTS |
Copyright: Copyright (c) 2006 by Apple Computer, Inc., All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under Apple's |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): |
$Log: MFSLivesUtil.c,v $ |
Revision 1.2 2006/10/17 15:21:05 eskimo1 |
Move the mount and utility tools into Contents/Resources. |
Revision 1.1 2006/07/27 15:48:15 eskimo1 |
First checked in. |
*/ |
///////////////////////////////////////////////////////////////////// |
// System interfaces |
#include <assert.h> |
#include <errno.h> |
#include <stdbool.h> |
#include <string.h> |
#include <stdlib.h> |
#include <stdio.h> |
#include <getopt.h> |
#include <fcntl.h> |
#include <time.h> |
#include <sys/stat.h> |
#include <sys/disk.h> |
#include <sys/loadable_fs.h> |
#include <sys/mman.h> |
#include <sys/xattr.h> |
// MFS core code |
#include "MFSCore.h" |
#include "MFSLivesPseudoMount.h" |
///////////////////////////////////////////////////////////////////// |
static FILE * gLog; |
static int gVerbose; |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** Commands to Support DiskArb |
static int ProbeCommand(const char *diskDeviceName) |
// Probe the disk specified by diskDeviceName (this is of the form |
// "disk1", that is, a block device name, not a path) to see if it's |
// an MFS disk. Returns FSUR_RECOGNIZED if it is, and FSUR_UNRECOGNIZED |
// otherwise. |
{ |
int err; |
int junk; |
int fd; |
char rawDevPath[MAXPATHLEN]; |
uint32_t blockSize; |
uint64_t blockCount; |
void * buf; |
size_t mdbAndVABMSizeInBytes; |
uint16_t directoryStartBlock; |
uint16_t directoryBlockCount; |
uint16_t allocationBlocksStartBlock; |
uint32_t allocationBlockSizeInBytes; |
ssize_t bytesRead; |
ssize_t bytesWritten; |
char volumeName[MAXPATHLEN]; |
struct vfs_attr attr; |
assert(diskDeviceName != NULL); |
if (gLog != NULL) fprintf(gLog, "[%ld] Probe '%s'\n", (long) getpid(), diskDeviceName); |
buf = NULL; |
// Construct the path to the character (raw) device, for example, |
// "/dev/rdisk1" and open it up. |
snprintf(rawDevPath, sizeof(rawDevPath), "/dev/r%s", diskDeviceName); |
err = 0; |
fd = open(rawDevPath, O_RDONLY); |
if (fd < 0) { |
err = errno; |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] open '%s' -> %d\n", (long) getpid(), rawDevPath, err); |
// Get its block size and fail if it's not 512. |
if (err == 0) { |
blockSize = 0; |
err = ioctl(fd, DKIOCGETBLOCKSIZE, &blockSize); |
if (err < 0) { |
err = errno; |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] ioctl DKIOCGETBLOCKSIZE -> %d, %lu\n", (long) getpid(), err, (long) blockSize); |
} |
if ( (err == 0) && (blockSize != 512) ) { |
err = EINVAL; |
} |
// Allocate a buffer, and read the MFS MDB block into it. |
if (err == 0) { |
buf = malloc(blockSize); |
if (buf == NULL) { |
err = ENOMEM; |
} |
} |
if (err == 0) { |
bytesRead = pread(fd, buf, blockSize, kMFSMDBBlock * blockSize); |
if (bytesRead < 0) { |
err = errno; |
} else if (bytesRead != blockSize) { |
err = EINVAL; |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] read %lu -> %d\n", (long) getpid(), (long) blockSize, err); |
} |
// Get the block count, because MFSMDBCheck wants it in order to do proper checking. |
if (err == 0) { |
blockCount = 0; |
err = ioctl(fd, DKIOCGETBLOCKCOUNT, &blockCount); |
if (err < 0) { |
err = errno; |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] ioctl DKIOCGETBLOCKCOUNT -> %d, %llu\n", (long) getpid(), err, (long long) blockCount); |
} |
// Call the MFS core code to see if it looks like an MFS disk. |
if (err == 0) { |
mdbAndVABMSizeInBytes = 0; |
err = MFSMDBCheck(buf, blockCount, &mdbAndVABMSizeInBytes, &directoryStartBlock, &directoryBlockCount, &allocationBlocksStartBlock, &allocationBlockSizeInBytes); |
if (gLog != NULL) fprintf(gLog, "[%ld] MFSMDBCheck -> %d, %zu\n", (long) getpid(), err, mdbAndVABMSizeInBytes); |
// If the check fails, log what went wrong. |
if ( (err == EINVAL) && (gLog != NULL) ) { |
char errStr[256]; |
MFSMDBGetError(buf, blockCount, errStr, sizeof(errStr)); |
fprintf(gLog, "[%ld] MFSMDBGetError -> %s\n", (long) getpid(), errStr); |
} |
} |
// If so, free the current buffer, allocate a new buffer big enough to contain the entire |
// MDB and VABM, and fill it. |
if (err == 0) { |
mdbAndVABMSizeInBytes = (mdbAndVABMSizeInBytes + (blockSize - 1)) / blockSize * blockSize; |
free(buf); |
buf = malloc(mdbAndVABMSizeInBytes); |
if (buf == NULL) { |
err = ENOMEM; |
} |
} |
if (err == 0) { |
bytesRead = pread(fd, buf, mdbAndVABMSizeInBytes, kMFSMDBBlock * blockSize); |
if (bytesRead < 0) { |
err = errno; |
} else if (bytesRead != mdbAndVABMSizeInBytes) { |
err = EINVAL; |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] read %zu -> %d\n", (long) getpid(), mdbAndVABMSizeInBytes, err); |
} |
// Call the MFS core code to get the volume name. |
if (err == 0) { |
VFSATTR_INIT(&attr); |
VFSATTR_WANTED(&attr, f_vol_name); |
attr.f_vol_name = volumeName; |
err = MFSMDBGetAttr(buf, &attr); |
if (err != 0) { |
volumeName[0] = 0; |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] MFSMDBGetAttr -> %d, '%s'\n", (long) getpid(), err, volumeName); |
} |
// Print the volume name to stdout, which is where DiskArb expects to pick it up. |
if (err == 0) { |
// MFSCore returns the data in UTF-8 format, which is exactly what we need. |
// We append a return purely for the sake of human users; DiskArb will be |
// happy either way. An MFS volume name is /way/ less than MAXPATHLEN, so |
// there's plenty of space for this extra character. |
strlcat(volumeName, "\n", sizeof(volumeName)); |
bytesWritten = write(STDOUT_FILENO, volumeName, strlen(volumeName)); |
if (bytesWritten < 0) { |
err = errno; |
} else if (bytesWritten != strlen(volumeName)) { |
err = EINVAL; |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] write -> %d\n", (long) getpid(), err); |
} |
// Clean up. |
free(buf); |
if (fd != -1) { |
junk = close(fd); |
assert(junk == 0); |
} |
// If we're running as root and we successfully recognise a volume, create |
// a symlink from "/sbin/mount_MFSLives" to "/System/Library/Filesystems/MFSLives.fs/Contents/Resources/mount_MFSLives". |
// I do this because: |
// |
// a) The system expects to find the mount_MFSLives tool in /sbin, and things don't |
// work if it's not there. Specifically, mount -t MFSLives will only find the |
// mount tool in /sbin, and that's what DiskArb uses to mount the disk. |
// |
// b) I don't want to place the real tool there, because I prefer everything to be |
// bundled up together in my .fs bundle. |
// |
// c) I don't want a separate installation step (installer, whatever) to be responsible |
// for creating this symlink. |
// |
// Note that I ignore any failure from this operation. I just let DiskArb get |
// and handle the resulting mount -t failure. This simplifies matters because |
// I /always/ try to create the symlink. If it already exists, it just fails. |
// |
// I hard-code the paths (as opposed to, say, using NSGetExecutablePath to find |
// my own path, and use that to track down mount_MFSLives tool) because it makes |
// the security implications much easier to understand. |
// |
// I only do this if I'm run as root (both effective and real user IDs) because |
// a) it will only work if EUID is 0, and there's no point trying otherwise, and |
// b) any attacker that's already root doesn't need my help. |
// |
// There shouldn't be any serious security gotchas here because both /sbin |
// and /System/Library/Filesystems are only writable by root. |
if ( (getuid() == 0) && (geteuid() == 0) ) { |
int err2; |
err2 = symlink("/System/Library/Filesystems/MFSLives.fs/Contents/Resources/mount_MFSLives", "/sbin/mount_MFSLives"); |
if (err2 < 0) { |
err2 = errno; |
} |
if (gLog != NULL) { |
if (err2 == EEXIST) { |
fprintf(gLog, "[%ld] symlink already exists\n", (long) getpid()); |
} else { |
fprintf(gLog, "[%ld] symlink -> %d\n", (long) getpid(), err2); |
} |
} |
} |
return (err == 0) ? FSUR_RECOGNIZED : FSUR_UNRECOGNIZED; |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** Commands For The Users |
// IMPORTANT |
// The List and Extract commands are /not/ required by Disk Arbitration. |
// They're here for the convenience of the user (so you don't have to risk |
// life and limb installing a kernel extension just to get files off your |
// MFS disks). |
#pragma mark - List Command |
static void OSTypeToUTF8String(uint8_t *ostPtr, char *utf8Name, size_t utf8NameSize) |
// Convert an OSType to a UTF-8 string, handling byte reversal, the possibility |
// of null characters, and the MacRoman-to-UTF-8 conversion. Why is this so hard? |
{ |
uint32_t ost; |
uint8_t pstr[5]; |
int i; |
size_t sizeNeeded; |
// Do the byte swap. |
ost = OSReadBigInt32(ostPtr, 0); |
// Convert zeros to spaces. |
pstr[0] = 4; |
for (i = 3; i >= 0; i--) { |
uint8_t ch; |
ch = ost & 0x00FF; |
ost >>= 8; |
if (ch == 0) { |
ch = ' '; |
} |
pstr[i + 1] = ch; |
} |
// Convert to UTF-8. |
sizeNeeded = MFSNameToUTF8(pstr, utf8Name, utf8NameSize); |
assert(sizeNeeded <= utf8NameSize); // if this fails, we've truncated |
} |
static void PrintDirectoryEntry(const struct vnode_attr *attr, uint8_t finderInfo[], const MFSForkInfo forkInfos[]) |
// Pretty prints an MFS directory entry in one of three ways depending on |
// the setting of gVerbose. |
// |
// finderInfo points to an array of 16 bytes. |
// forkInfos points to an array of two elements, indexed by the forkIndex. |
{ |
char fileTypeStr[32]; |
char fileCreatorStr[32]; |
int i; |
struct tm tm; |
char dateTimeStr[256]; |
size_t size; |
assert(attr != NULL); |
assert(finderInfo != NULL); |
assert(forkInfos != NULL); |
switch (gVerbose) { |
case 0: |
fprintf(stdout, "%s\n", attr->va_name); |
break; |
case 1: |
OSTypeToUTF8String(&finderInfo[0], fileTypeStr, sizeof(fileTypeStr) ); |
OSTypeToUTF8String(&finderInfo[4], fileCreatorStr, sizeof(fileCreatorStr)); |
// type crea size size name |
fprintf(stdout, "%10u %s %s %10u %10u %s\n", (unsigned int) attr->va_fileid, fileTypeStr, fileCreatorStr, forkInfos[0].lengthInBytes, forkInfos[1].lengthInBytes, attr->va_name); |
break; |
default: |
fprintf(stdout, "name: %s\n", attr->va_name); |
fprintf(stdout, "fileNumber: %u\n", (unsigned int) attr->va_fileid); |
fprintf(stdout, "finderInfo:"); |
for (i = 0; i < 16; i++) { |
fprintf(stdout, " %02x", finderInfo[i]); |
} |
fprintf(stdout, "\n"); |
fprintf(stdout, "dataLengthInBytes: %u\n", forkInfos[0].lengthInBytes); |
fprintf(stdout, "dataPhysicalLengthInBytes: %u\n", forkInfos[0].physicalLengthInBytes); |
fprintf(stdout, "rsrcLengthInBytes: %u\n", forkInfos[1].lengthInBytes); |
fprintf(stdout, "rsrcPhysicalLengthInBytes: %u\n", forkInfos[1].physicalLengthInBytes); |
// For an explanation of why I use gmtime_r and not localtime_r here, see |
// the "Dates/Time Values" comment in "MFSCore.h". |
(void) gmtime_r(&attr->va_create_time.tv_sec, &tm); |
size = strftime(dateTimeStr, sizeof(dateTimeStr), "%a, %d %b %Y %H:%M:%S %Z", &tm); |
assert(size != 0); |
fprintf(stdout, "creationDate: %s\n", dateTimeStr); |
(void) gmtime_r(&attr->va_modify_time.tv_sec, &tm); |
size = strftime(dateTimeStr, sizeof(dateTimeStr), "%a, %d %b %Y %H:%M:%S %Z", &tm); |
assert(size != 0); |
fprintf(stdout, "modificationDate: %s\n", dateTimeStr); |
break; |
} |
} |
static int ListCommand(const char *containerPath) |
// Implements the list command. Pseudo mounts the 'volume' and iterates |
// each directory block, printing the results. |
{ |
int err; |
MFSPMountRef pmount; |
size_t fileCountToAlloc; |
size_t fileCount; |
size_t fileIndex; |
MFSPMountFileInfo * files; |
struct vnode_attr attr; |
assert(containerPath != NULL); |
if (gLog != NULL) fprintf(gLog, "[%ld] List '%s'\n", (long) getpid(), containerPath); |
pmount = NULL; |
files = NULL; |
// Pseudo mount the 'volume'. |
err = MFSPMountCreate(containerPath, &pmount); |
// Get the directory list. |
if (err == 0) { |
err = MFSPMountListFiles(pmount, NULL, 0, &fileCountToAlloc); |
} |
if (err == 0) { |
files = malloc(fileCountToAlloc * sizeof(*files)); |
if (files == NULL) { |
err = ENOMEM; |
} |
} |
if (err == 0) { |
err = MFSPMountListFiles(pmount, files, fileCountToAlloc, &fileCount); |
} |
// Print it. |
if (err == 0) { |
assert(fileCount == fileCountToAlloc); |
for (fileIndex = 0; fileIndex < fileCount; fileIndex++) { |
char name[MAXPATHLEN]; |
MFSForkInfo forkInfos[2]; |
uint8_t finderInfo[16]; |
name[0] = 0; // init to empty string to allow logging even if we get an error |
// Gather all of the necessary information about the directory entry. |
VATTR_INIT(&attr); |
attr.va_name = name; |
VATTR_WANTED(&attr, va_name); |
VATTR_WANTED(&attr, va_fileid); |
VATTR_WANTED(&attr, va_create_time); |
VATTR_WANTED(&attr, va_modify_time); |
err = MFSDirectoryEntryGetAttr( |
files[fileIndex].dirBlockPtr, |
files[fileIndex].dirOffset, |
&attr |
); |
if (err == 0) { |
err = MFSDirectoryEntryGetFinderInfo(files[fileIndex].dirBlockPtr, files[fileIndex].dirOffset, &finderInfo); |
} |
if (err == 0) { |
err = MFSDirectoryEntryGetForkInfo(files[fileIndex].dirBlockPtr, files[fileIndex].dirOffset, 0, &forkInfos[0]); |
} |
if (err == 0) { |
err = MFSDirectoryEntryGetForkInfo(files[fileIndex].dirBlockPtr, files[fileIndex].dirOffset, 1, &forkInfos[1]); |
} |
if (gLog != NULL) fprintf(gLog, "[%ld] %3d %3zu %3zu '%s'\n", (long) getpid(), err, fileIndex, files[fileIndex].dirOffset, name); |
// Now that we have everything we need to know about this directory entry, |
// let's print it. |
if (err == 0) { |
// It's kinda ugly testing gVerbose here, but I prefer it to passing fileIndex |
// to PrintDirectoryEntry. |
if ( (fileIndex > 0) && (gVerbose > 1) ) { |
fprintf(stdout, "\n"); |
} |
PrintDirectoryEntry(&attr, finderInfo, forkInfos); |
} |
} |
} |
// Clean up. |
free(files); |
MFSPMountDestroy(pmount); |
// Print the error, unless the underlying code has already done so (indicated |
// by it return ECANCELED). |
if ( (err != 0) && (err != ECANCELED) ) { |
errno = err; |
perror(NULL); |
} |
return ((err == 0) ? EXIT_SUCCESS : EXIT_FAILURE); |
} |
#pragma mark - Extract Command |
static int ExtractCommand(const char *containerPath, const char *fileName, const char *outputFilePath) |
// Implements the extract command. Pseudo mounts the 'volume', finds the file whose |
// name is fileName, and extracts it to a newly created file at outputFilePath (or, |
// if outputFilePath is NULL, to a newly created file named fileName in the current |
// working directory). |
{ |
int err; |
MFSPMountRef pmount; |
assert(containerPath != NULL); |
assert(fileName != NULL); |
// outputFilePath may be NULL |
if (gLog != NULL) fprintf(gLog, "[%ld] Extract '%s' '%s' '%s'\n", (long) getpid(), containerPath, fileName, ((outputFilePath != NULL) ? outputFilePath : "") ); |
pmount = NULL; |
// Initialise the MFS core. |
err = MFSPMountCreate(containerPath, &pmount); |
// Do the work. |
if (err == 0) { |
err = MFSPMountExtractFile(pmount, fileName, outputFilePath); |
} |
// Clean up. |
MFSPMountDestroy(pmount); |
// Print the error, unless the underlying code has already done so (indicated |
// by it return ECANCELED). |
if ( (err != 0) && (err != ECANCELED) ) { |
errno = err; |
perror(NULL); |
} |
return ((err == 0) ? EXIT_SUCCESS : EXIT_FAILURE); |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** Main etc |
static void PrintUsage(const char *argv0) |
// Print a helpful help message. |
{ |
const char * progName; |
progName = strrchr(argv0, '/'); |
if (progName == NULL) { |
progName = argv0; |
} else { |
progName += 1; |
} |
fprintf(stderr, "usage: %s [-v] -p diskDeviceName info...\n", progName); |
fprintf(stderr, " %s [-v] -L containerPath\n", progName); |
fprintf(stderr, " %s [-v] -X containerPath fileName [ outputFilePath ]\n", progName); |
fprintf(stderr, " where:\n"); |
fprintf(stderr, " o diskDeviceName is the name of a disk device (for example, 'disk1')\n"); |
fprintf(stderr, " o containerPath is the path to a Disk Copy 4.2 file (.img), a raw disk \n"); |
fprintf(stderr, " image file (typically .bin, or .cdr, or .iso), or a cooked or raw \n"); |
fprintf(stderr, " disk device (for example, '/dev/disk1' or '/dev/rdisk1')\n"); |
} |
extern int main(int argc, char **argv) |
{ |
int retVal; |
static const char *kLogPath = "/var/log/MFSLives.util.log"; |
struct stat junkSB; |
int ch; |
enum { |
kCommandUnspecified, |
kCommandProbe, |
kCommandList, |
kCommandExtract |
} command; |
// Set up logging |
// Because the log file is in a directory that's only writable by root, |
// there are no nasty security gotchas here. |
gLog = NULL; |
if ( stat(kLogPath, &junkSB) == 0 ) { |
gLog = fopen(kLogPath, "a"); |
} |
if (gLog != NULL) { |
int argIndex; |
fprintf(gLog, "[%ld] launch (%ld, %ld):\n", (long) getpid(), (long) geteuid(), (long) getuid()); |
for (argIndex = 0; argIndex < argc; argIndex++) { |
fprintf(gLog, "[%ld] arg[%d] = '%s'\n", (long) getpid(), argIndex, argv[argIndex]); |
} |
fflush(gLog); |
(void) setvbuf(gLog, NULL, _IONBF, 0); |
MFSPMountSetLogFile(gLog); |
} |
// Parse command line options. |
command = kCommandUnspecified; |
retVal = FSUR_IO_SUCCESS; |
do { |
ch = getopt(argc, argv, "vpLX"); |
if (ch != -1) { |
switch (ch) { |
case 'v': |
gVerbose += 1; |
break; |
case 'p': |
if (command == kCommandUnspecified) { |
command = kCommandProbe; |
} else { |
PrintUsage(argv[0]); |
retVal = FSUR_INVAL; |
} |
break; |
case 'L': |
if (command == kCommandUnspecified) { |
command = kCommandList; |
} else { |
PrintUsage(argv[0]); |
retVal = FSUR_INVAL; |
} |
break; |
case 'X': |
if (command == kCommandUnspecified) { |
command = kCommandExtract; |
} else { |
PrintUsage(argv[0]); |
retVal = FSUR_INVAL; |
} |
break; |
case '?': |
default: |
PrintUsage(argv[0]); |
retVal = FSUR_INVAL; |
break; |
} |
} |
} while ( (ch != -1) && (retVal == FSUR_IO_SUCCESS) ); |
// Do the commands. |
if (retVal == FSUR_IO_SUCCESS) { |
bool printUsage; |
printUsage = false; |
switch (command) { |
case kCommandProbe: |
if (optind < argc) { // we ignore arguments after the device name |
retVal = ProbeCommand(argv[optind]); |
} else { |
PrintUsage(argv[0]); |
retVal = FSUR_INVAL; |
} |
break; |
case kCommandList: |
if ( (argc - optind) == 1 ) { |
retVal = ListCommand(argv[optind]); |
} else { |
printUsage = true; |
} |
break; |
case kCommandExtract: |
if ( ((argc - optind) == 2) || ((argc - optind) == 3) ) { |
retVal = ExtractCommand(argv[optind], argv[optind + 1], argv[optind + 2]); |
} else { |
printUsage = true; |
} |
break; |
default: |
PrintUsage(argv[0]); |
retVal = FSUR_INVAL; |
break; |
} |
if (printUsage) { |
PrintUsage(argv[0]); |
retVal = FSUR_INVAL; |
} |
} |
if (gLog) { |
fprintf(gLog, "[%ld] returning %d\n", (long) getpid(), retVal); |
} |
return retVal; |
} |
Copyright © 2006 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2006-11-09