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.
MFSCore.c
/* |
File: MFSCore.c |
Contains: Core MFS implementation for MFSLives. |
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: MFSCore.c,v $ |
Revision 1.1 2006/07/27 15:47:43 eskimo1 |
First checked in. |
*/ |
///////////////////////////////////////////////////////////////////// |
#include "MFSCore.h" |
#if KERNEL |
#include <kern/assert.h> |
#else |
#include <assert.h> |
#endif |
#include <string.h> |
#include <sys/errno.h> |
#include <sys/stat.h> |
#include <libkern/OSByteOrder.h> |
#if KERNEL |
#include <sys/utfconv.h> |
#else |
// We need the utf8_decodestr routine, but it's not publically available |
// to user space code. So we implement our own version, but only for |
// user space. |
#include "utf8_decodestr.h" |
// In kernel space, user space, offsetof comes <sys/_types.h>, which you get |
// implicitly from an include above. In user space, we have to explicitly |
// bring it in from <stddef.h>. |
#include <stddef.h> |
#endif |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** String Conversion Tables |
// These tables are explained in detail in comments in the header. |
// These tables were generated by the TableGenerator program (see "TableGenerator.c"). |
static const char * kMacRomanToUTF8[128] = { |
/* 0x80 */ /* U+0041 U+0308 */ "A\xCC\x88", /* U+0041 U+030A */ "A\xCC\x8A", /* U+0043 U+0327 */ "C\xCC\xA7", /* U+0045 U+0301 */ "E\xCC\x81", |
/* 0x84 */ /* U+004E U+0303 */ "N\xCC\x83", /* U+004F U+0308 */ "O\xCC\x88", /* U+0055 U+0308 */ "U\xCC\x88", /* U+0061 U+0301 */ "a\xCC\x81", |
/* 0x88 */ /* U+0061 U+0300 */ "a\xCC\x80", /* U+0061 U+0302 */ "a\xCC\x82", /* U+0061 U+0308 */ "a\xCC\x88", /* U+0061 U+0303 */ "a\xCC\x83", |
/* 0x8C */ /* U+0061 U+030A */ "a\xCC\x8A", /* U+0063 U+0327 */ "c\xCC\xA7", /* U+0065 U+0301 */ "e\xCC\x81", /* U+0065 U+0300 */ "e\xCC\x80", |
/* 0x90 */ /* U+0065 U+0302 */ "e\xCC\x82", /* U+0065 U+0308 */ "e\xCC\x88", /* U+0069 U+0301 */ "i\xCC\x81", /* U+0069 U+0300 */ "i\xCC\x80", |
/* 0x94 */ /* U+0069 U+0302 */ "i\xCC\x82", /* U+0069 U+0308 */ "i\xCC\x88", /* U+006E U+0303 */ "n\xCC\x83", /* U+006F U+0301 */ "o\xCC\x81", |
/* 0x98 */ /* U+006F U+0300 */ "o\xCC\x80", /* U+006F U+0302 */ "o\xCC\x82", /* U+006F U+0308 */ "o\xCC\x88", /* U+006F U+0303 */ "o\xCC\x83", |
/* 0x9C */ /* U+0075 U+0301 */ "u\xCC\x81", /* U+0075 U+0300 */ "u\xCC\x80", /* U+0075 U+0302 */ "u\xCC\x82", /* U+0075 U+0308 */ "u\xCC\x88", |
/* 0xA0 */ /* U+2020 */ "\xE2\x80\xA0", /* U+00B0 */ "\xC2\xB0", /* U+00A2 */ "\xC2\xA2", /* U+00A3 */ "\xC2\xA3", |
/* 0xA4 */ /* U+00A7 */ "\xC2\xA7", /* U+2022 */ "\xE2\x80\xA2", /* U+00B6 */ "\xC2\xB6", /* U+00DF */ "\xC3\x9F", |
/* 0xA8 */ /* U+00AE */ "\xC2\xAE", /* U+00A9 */ "\xC2\xA9", /* U+2122 */ "\xE2\x84\xA2", /* U+00B4 */ "\xC2\xB4", |
/* 0xAC */ /* U+00A8 */ "\xC2\xA8", /* U+003D U+0338 */ "=\xCC\xB8", /* U+00C6 */ "\xC3\x86", /* U+00D8 */ "\xC3\x98", |
/* 0xB0 */ /* U+221E */ "\xE2\x88\x9E", /* U+00B1 */ "\xC2\xB1", /* U+2264 */ "\xE2\x89\xA4", /* U+2265 */ "\xE2\x89\xA5", |
/* 0xB4 */ /* U+00A5 */ "\xC2\xA5", /* U+00B5 */ "\xC2\xB5", /* U+2202 */ "\xE2\x88\x82", /* U+2211 */ "\xE2\x88\x91", |
/* 0xB8 */ /* U+220F */ "\xE2\x88\x8F", /* U+03C0 */ "\xCF\x80", /* U+222B */ "\xE2\x88\xAB", /* U+00AA */ "\xC2\xAA", |
/* 0xBC */ /* U+00BA */ "\xC2\xBA", /* U+03A9 */ "\xCE\xA9", /* U+00E6 */ "\xC3\xA6", /* U+00F8 */ "\xC3\xB8", |
/* 0xC0 */ /* U+00BF */ "\xC2\xBF", /* U+00A1 */ "\xC2\xA1", /* U+00AC */ "\xC2\xAC", /* U+221A */ "\xE2\x88\x9A", |
/* 0xC4 */ /* U+0192 */ "\xC6\x92", /* U+2248 */ "\xE2\x89\x88", /* U+2206 */ "\xE2\x88\x86", /* U+00AB */ "\xC2\xAB", |
/* 0xC8 */ /* U+00BB */ "\xC2\xBB", /* U+2026 */ "\xE2\x80\xA6", /* U+00A0 */ "\xC2\xA0", /* U+0041 U+0300 */ "A\xCC\x80", |
/* 0xCC */ /* U+0041 U+0303 */ "A\xCC\x83", /* U+004F U+0303 */ "O\xCC\x83", /* U+0152 */ "\xC5\x92", /* U+0153 */ "\xC5\x93", |
/* 0xD0 */ /* U+2013 */ "\xE2\x80\x93", /* U+2014 */ "\xE2\x80\x94", /* U+201C */ "\xE2\x80\x9C", /* U+201D */ "\xE2\x80\x9D", |
/* 0xD4 */ /* U+2018 */ "\xE2\x80\x98", /* U+2019 */ "\xE2\x80\x99", /* U+00F7 */ "\xC3\xB7", /* U+25CA */ "\xE2\x97\x8A", |
/* 0xD8 */ /* U+0079 U+0308 */ "y\xCC\x88", /* U+0059 U+0308 */ "Y\xCC\x88", /* U+2044 */ "\xE2\x81\x84", /* U+20AC */ "\xE2\x82\xAC", |
/* 0xDC */ /* U+2039 */ "\xE2\x80\xB9", /* U+203A */ "\xE2\x80\xBA", /* U+FB01 */ "\xEF\xAC\x81", /* U+FB02 */ "\xEF\xAC\x82", |
/* 0xE0 */ /* U+2021 */ "\xE2\x80\xA1", /* U+00B7 */ "\xC2\xB7", /* U+201A */ "\xE2\x80\x9A", /* U+201E */ "\xE2\x80\x9E", |
/* 0xE4 */ /* U+2030 */ "\xE2\x80\xB0", /* U+0041 U+0302 */ "A\xCC\x82", /* U+0045 U+0302 */ "E\xCC\x82", /* U+0041 U+0301 */ "A\xCC\x81", |
/* 0xE8 */ /* U+0045 U+0308 */ "E\xCC\x88", /* U+0045 U+0300 */ "E\xCC\x80", /* U+0049 U+0301 */ "I\xCC\x81", /* U+0049 U+0302 */ "I\xCC\x82", |
/* 0xEC */ /* U+0049 U+0308 */ "I\xCC\x88", /* U+0049 U+0300 */ "I\xCC\x80", /* U+004F U+0301 */ "O\xCC\x81", /* U+004F U+0302 */ "O\xCC\x82", |
/* 0xF0 */ /* U+F8FF */ "\xEF\xA3\xBF", /* U+004F U+0300 */ "O\xCC\x80", /* U+0055 U+0301 */ "U\xCC\x81", /* U+0055 U+0302 */ "U\xCC\x82", |
/* 0xF4 */ /* U+0055 U+0300 */ "U\xCC\x80", /* U+0131 */ "\xC4\xB1", /* U+02C6 */ "\xCB\x86", /* U+02DC */ "\xCB\x9C", |
/* 0xF8 */ /* U+00AF */ "\xC2\xAF", /* U+02D8 */ "\xCB\x98", /* U+02D9 */ "\xCB\x99", /* U+02DA */ "\xCB\x9A", |
/* 0xFC */ /* U+00B8 */ "\xC2\xB8", /* U+02DD */ "\xCB\x9D", /* U+02DB */ "\xCB\x9B", /* U+02C7 */ "\xCB\x87" |
}; |
const int kMacRomanToUTF8Expansion = 3; |
struct UniCharInfo { |
uint16_t utf16Char; |
uint8_t macRomanChar; |
}; |
typedef struct UniCharInfo UniCharInfo; |
static const UniCharInfo kUTF16ToMacRoman[128] = { |
{0x00A0, 0xCA}, {0x00A1, 0xC1}, {0x00A2, 0xA2}, {0x00A3, 0xA3}, {0x00A5, 0xB4}, {0x00A7, 0xA4}, {0x00A8, 0xAC}, {0x00A9, 0xA9}, |
{0x00AA, 0xBB}, {0x00AB, 0xC7}, {0x00AC, 0xC2}, {0x00AE, 0xA8}, {0x00AF, 0xF8}, {0x00B0, 0xA1}, {0x00B1, 0xB1}, {0x00B4, 0xAB}, |
{0x00B5, 0xB5}, {0x00B6, 0xA6}, {0x00B7, 0xE1}, {0x00B8, 0xFC}, {0x00BA, 0xBC}, {0x00BB, 0xC8}, {0x00BF, 0xC0}, {0x00C0, 0xCB}, |
{0x00C1, 0xE7}, {0x00C2, 0xE5}, {0x00C3, 0xCC}, {0x00C4, 0x80}, {0x00C5, 0x81}, {0x00C6, 0xAE}, {0x00C7, 0x82}, {0x00C8, 0xE9}, |
{0x00C9, 0x83}, {0x00CA, 0xE6}, {0x00CB, 0xE8}, {0x00CC, 0xED}, {0x00CD, 0xEA}, {0x00CE, 0xEB}, {0x00CF, 0xEC}, {0x00D1, 0x84}, |
{0x00D2, 0xF1}, {0x00D3, 0xEE}, {0x00D4, 0xEF}, {0x00D5, 0xCD}, {0x00D6, 0x85}, {0x00D8, 0xAF}, {0x00D9, 0xF4}, {0x00DA, 0xF2}, |
{0x00DB, 0xF3}, {0x00DC, 0x86}, {0x00DF, 0xA7}, {0x00E0, 0x88}, {0x00E1, 0x87}, {0x00E2, 0x89}, {0x00E3, 0x8B}, {0x00E4, 0x8A}, |
{0x00E5, 0x8C}, {0x00E6, 0xBE}, {0x00E7, 0x8D}, {0x00E8, 0x8F}, {0x00E9, 0x8E}, {0x00EA, 0x90}, {0x00EB, 0x91}, {0x00EC, 0x93}, |
{0x00ED, 0x92}, {0x00EE, 0x94}, {0x00EF, 0x95}, {0x00F1, 0x96}, {0x00F2, 0x98}, {0x00F3, 0x97}, {0x00F4, 0x99}, {0x00F5, 0x9B}, |
{0x00F6, 0x9A}, {0x00F7, 0xD6}, {0x00F8, 0xBF}, {0x00F9, 0x9D}, {0x00FA, 0x9C}, {0x00FB, 0x9E}, {0x00FC, 0x9F}, {0x00FF, 0xD8}, |
{0x0131, 0xF5}, {0x0152, 0xCE}, {0x0153, 0xCF}, {0x0178, 0xD9}, {0x0192, 0xC4}, {0x02C6, 0xF6}, {0x02C7, 0xFF}, {0x02D8, 0xF9}, |
{0x02D9, 0xFA}, {0x02DA, 0xFB}, {0x02DB, 0xFE}, {0x02DC, 0xF7}, {0x02DD, 0xFD}, {0x03A9, 0xBD}, {0x03C0, 0xB9}, {0x2013, 0xD0}, |
{0x2014, 0xD1}, {0x2018, 0xD4}, {0x2019, 0xD5}, {0x201A, 0xE2}, {0x201C, 0xD2}, {0x201D, 0xD3}, {0x201E, 0xE3}, {0x2020, 0xA0}, |
{0x2021, 0xE0}, {0x2022, 0xA5}, {0x2026, 0xC9}, {0x2030, 0xE4}, {0x2039, 0xDC}, {0x203A, 0xDD}, {0x2044, 0xDA}, {0x20AC, 0xDB}, |
{0x2122, 0xAA}, {0x2202, 0xB6}, {0x2206, 0xC6}, {0x220F, 0xB8}, {0x2211, 0xB7}, {0x221A, 0xC3}, {0x221E, 0xB0}, {0x222B, 0xBA}, |
{0x2248, 0xC5}, {0x2260, 0xAD}, {0x2264, 0xB2}, {0x2265, 0xB3}, {0x25CA, 0xD7}, {0xF8FF, 0xF0}, {0xFB01, 0xDE}, {0xFB02, 0xDF}, |
}; |
static const uint16_t kMacRomanToUpper[256] = { |
/* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, |
/* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, |
/* 0x20 */ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, |
/* 0x30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, |
/* 0x40 */ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, |
/* 0x50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, |
/* 0x60 */ 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, |
/* 0x70 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, |
/* 0x80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x80, 0x8B, 0x81, 0x82, 0x83, 0x8F, |
/* 0x90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x84, 0x97, 0x98, 0x99, 0x85, 0x9B, 0x9C, 0x9D, 0x9E, 0x86, |
/* 0xA0 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, |
/* 0xB0 */ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xAE, 0xAF, |
/* 0xC0 */ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x88, 0x8B, 0x9B, 0xCE, 0xCE, |
/* 0xD0 */ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD8, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, |
/* 0xE0 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x89, 0x90, 0x87, 0x91, 0x8F, 0x92, 0x94, 0x95, 0x93, 0x97, 0x99, |
/* 0xF0 */ 0xF0, 0x98, 0x9C, 0x9E, 0x9D, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF |
}; |
// End of automatically generated tables. |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** String Manipulation |
extern size_t MFSNameToUTF8(const uint8_t *name, char *utf8Name, size_t utf8NameSize) |
// Converts an MFS name to its UTF-8 equivalent. |
// |
// name must be a MacRoman encoded Pascal string. |
// |
// utf8Name must be a pointer to a buffer of utf8NameSize bytes. On |
// return, this will hold a UTF-8 (decomposed) C string equivalent to |
// name. |
// |
// utf8NameSize must be at least 1 (to hold the terminating null character |
// of the C string). |
// |
// Returns the size of the buffer that would be needed to hold a full |
// conversion of name. If this is less than utf8NameSize, the string |
// was truncated. If it was truncated, it was truncated at a MacRoman |
// character boundary. |
// |
// This routine is carefully crafted to drop any null characters embeddedd |
// in name. null characters are valid in MFS names (yikes!) but not in |
// C strings. |
{ |
int nameLen; |
int i; |
char * outputPtr; |
size_t outputTotalSize; |
const char * thisStr; |
size_t thisStrLen; |
assert(name != NULL); |
assert(utf8Name != NULL); |
assert(utf8NameSize > 0); // must at least give us room for null terminator |
outputPtr = utf8Name; |
outputTotalSize = 0; |
nameLen = name[0]; |
for (i = 1; i <= nameLen; i++) { |
uint8_t ch; |
ch = name[i]; |
if (ch < 128) { |
thisStr = (const char *) &ch; |
thisStrLen = (ch != 0); // if ch is 0, thisStrLen is 0, so we add no bytes to the string |
} else { |
thisStr = kMacRomanToUTF8[ch - 128]; |
thisStrLen = strlen(thisStr); |
} |
outputTotalSize += thisStrLen; |
if (outputTotalSize < utf8NameSize) { // strictly less than guarantees that we always have space for null terminator |
memcpy(outputPtr, thisStr, thisStrLen); |
outputPtr += thisStrLen; |
} |
} |
assert(outputPtr < (utf8Name + utf8NameSize)); |
*outputPtr = 0; |
return outputTotalSize + 1; // including null terminator |
} |
extern errno_t UTF8ToMFSName(const char *utf8Name, size_t utf8NameLen, void *tempBuffer, uint8_t *mfsName) |
// Converts a UTF-8 encoding string (either precomposed or decomposed) to |
// an MFS name (a MacRoman encoded Pascal string). |
// |
// utf8Name must point to a UTF-8 string containing utf8NameLen characters. |
// |
// tempBuffer must point to a temporary working buffer of kUTF8ToMFSNameTempBufferSize |
// bytes. On entry, its value is ignored. On return, its value is undefined. |
// |
// mfsName must point to a buffer of 256 bytes. On entry, its value is ignored. |
// On success, this contains the MacRoman Pascal string equivalent to utf8Name. |
// |
// Likely error results include: |
// |
// o EINVAL, which means that utf8Name contains Unicode characters that can't |
// be mapped into MacRoman |
// |
// o ENAMETOOLONG, which mean that utf8Name is too longer to be held in a |
// MFS name |
{ |
int err; |
uint16_t * utf16Ptr; |
size_t utf16Count; |
size_t utf16Index; |
assert(utf8Name != NULL); |
assert(tempBuffer != NULL); |
assert(mfsName != NULL); |
utf16Ptr = (uint16_t *) tempBuffer; |
err = utf8_decodestr( (const uint8_t *) utf8Name, utf8NameLen, utf16Ptr, &utf16Count, kUTF8ToMFSNameTempBufferSize, 0, UTF_PRECOMPOSED); |
if (err == 0) { |
utf16Count /= sizeof(utf16Ptr[0]); // it comes back as bytes, and we want chars |
assert(utf16Count <= 255); // because it's limited by kUTF8ToMFSNameTempBufferSize |
mfsName[0] = (uint8_t) utf16Count; // set up length byte |
for (utf16Index = 0; utf16Index < utf16Count; utf16Index++) { |
uint16_t key; |
// Binary search kUTF16ToMacRoman for this UTF-16 character. |
// This scary algorithm was stolen from the libc implementation |
// of <x-man-page://3/bsearch>. |
key = utf16Ptr[utf16Index]; |
if (key < 128) { |
// Both MacRoman and UTF-8 inherit their bottom 128 characters from |
// ASCII, so you can just copy it across. |
mfsName[utf16Index + 1] = (uint8_t) key; |
} else { |
size_t limit; |
int compareResult; |
const UniCharInfo * base; |
const UniCharInfo * this; |
boolean_t found; |
// Otherwise, we have to do it the hard way. |
found = FALSE; |
base = &kUTF16ToMacRoman[0]; |
for (limit = (sizeof(kUTF16ToMacRoman) / sizeof(kUTF16ToMacRoman[0])); limit != 0; limit >>= 1) { |
this = base + (limit >> 1); |
compareResult = ((int) key) - ((int) this->utf16Char); |
if (compareResult == 0) { |
found = TRUE; |
break; |
} |
if (compareResult > 0) { |
base = this + 1; |
limit -= 1; |
} |
} |
if (found) { |
mfsName[utf16Index + 1] = this->macRomanChar; // + 1 because the output is a Pascal string |
} else { |
err = EINVAL; |
break; |
} |
} |
} |
} |
return err; |
} |
extern void MFSNameToUpper(uint8_t *mfsName) |
// Converts the MFS name (a MacRoman encoding Pascal string) pointed to by |
// mfsName to upper case. |
// |
// mfsName must point to a valid MFS name. This routine makes no assumptions |
// about the size of the buffer containing this name (for example, if you |
// have a Str63, it's fine to call this routine on it as long as the |
// length of the string is 63 or less). |
{ |
int charCount; |
int charIndex; |
charCount = mfsName[0]; |
for (charIndex = 1; charIndex <= charCount; charIndex++) { |
mfsName[charIndex] = kMacRomanToUpper[mfsName[charIndex]]; |
} |
} |
extern boolean_t MFSNameEqualToUpper(const uint8_t *mfsName, const uint8_t *mfsNameUpper) |
// Does a case sensitive comparison of mfsName and mfsNameUpper. |
// |
// mfsName must point to a valid MFS name. |
// |
// mfsNameUpper must point to a valid MFS name that has already been |
// uppercased by calling MFSNameToUpper. |
// |
// Returns true if the strings are equal. |
{ |
boolean_t result; |
int charCount; |
int charIndex; |
charCount = mfsName[0]; |
if (charCount == mfsNameUpper[0]) { |
result = TRUE; |
for (charIndex = 1; charIndex <= charCount; charIndex++) { |
if ( kMacRomanToUpper[mfsName[charIndex]] != mfsNameUpper[charIndex] ) { |
result = FALSE; |
break; |
} |
} |
} else { |
result = FALSE; |
} |
return result; |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** Date/Time Manipulation |
extern struct timespec MFSDateTimeToTimeSpec(uint32_t mfsDateTime) |
// This routine converts an MFS date/time value to a standard BSD |
// (struct timespec), accounting for the different epoch. Note that |
// this doesn't do UTC conversion, for reasons that are discussed in |
// the "Dates/Time Values" comment in the header. |
{ |
// kMFSDateTimeToUNIXDelta is the number of seconds between |
// the start of the MFS date/time counter (00:00:00 1 Jan 1904) |
// and the start of the BSD date/time counter (00:00:00 1 Jan 1970). |
enum { |
kMFSDateTimeToUNIXDelta = 3600UL * 24 * ((365 * (1970 - 1904)) + (((1970 - 1904) / 4) + 1)) |
}; |
struct timespec result; |
// Correct for the difference between the MFS and UNIX epochs. |
if (mfsDateTime > kMFSDateTimeToUNIXDelta) { |
// The date/time is 1970 or later, and can be converted to a UNIX date/time |
// counter correctly. |
result.tv_sec = mfsDateTime - kMFSDateTimeToUNIXDelta; |
} else { |
// The date/time is before 1970, so we set it as low as it can go. |
result.tv_sec = 0; |
} |
result.tv_nsec = 0; |
return result; |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** MFS On-Disk Structures |
// The following structures map to the on-disk format of an MFS |
// MDB (MFSMasterDirectoryBlock) and directory entry (MFSDirectoryRecord). |
// See Inside Macintosh II for the definition of these structures, and a |
// discussion of the overall on-disk volume format. |
// |
// IMPORTANT |
// MFS is big endian, so any time you access a multibyte field, you must |
// swap it to host endian. |
enum { |
kMFSSigWord = 0xD2D7 |
}; |
struct __attribute__ ((__packed__)) MFSMasterDirectoryBlock { |
uint16_t sigWord; // -> sigWord |
uint32_t creationDate; |
uint32_t backupDate; |
uint16_t attributes; |
uint16_t fileCount; |
uint16_t directoryStartBlock; |
uint16_t directoryBlockCount; |
uint16_t allocationBlockCount; |
uint32_t allocationBlockSizeInBytes; |
uint32_t clumpSizeInBytes; |
uint16_t allocationBlocksStartBlock; |
uint32_t nextFileNumber; |
uint16_t freeAllocationBlockCount; |
uint8_t nameLength; |
uint8_t name[27]; |
}; |
typedef struct MFSMasterDirectoryBlock MFSMasterDirectoryBlock; |
struct __attribute__ ((__packed__)) MFSDirectoryRecord { |
uint8_t attributes; |
uint8_t versionNumber; |
uint8_t finderInfo[16]; |
uint32_t fileNumber; |
uint16_t dataFirstAllocationBlock; |
uint32_t dataLengthInBytes; |
uint32_t dataPhysicalLengthInBytes; |
uint16_t rsrcFirstAllocationBlock; |
uint32_t rsrcLengthInBytes; |
uint32_t rsrcPhysicalLengthInBytes; |
uint32_t creationDate; |
uint32_t modificationDate; |
uint8_t nameLength; |
uint8_t name[255]; |
// name field is of variable length and is followed, if the overall length |
// of the MFSDirectoryRecord is odd, by a pad byte whose value is zero |
}; |
typedef struct MFSDirectoryRecord MFSDirectoryRecord; |
enum { |
kMFSDirectoryRecordFixedSize = offsetof(MFSDirectoryRecord, name), // everything up to nameLength is fixed |
kMFSDirectoryRecordMinimumSize = kMFSDirectoryRecordFixedSize + 1 // +1 because each name must have at least one char |
}; |
// Constants for the attributes field of MFSDirectoryRecord. |
enum { |
kMFSDirectoryRecordAllocatedAttr = 0x80, |
kMFSDirectoryRecordMysteryAttr = 0x40, // [1] |
kMFSDirectoryRecordReservedAttr = 0x3E, // [1] |
kMFSDirectoryRecordLockedAttr = 0x01 |
}; |
// Notes |
// [1] In testing with my entire archive of MFS disks (67 in total), I discovered a few |
// files on a few disks that has the 0x40 bit set in their attributes. I have no idea |
// what this is; certainly, it's not described in the IM II. I have a sneaking suspicion |
// that it was some sort of copy protected bit, but I have no real evidence for that. |
// Regardless, I'm happy to ignore it. |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** MDB Routines |
static void SetErrorString(char *errStr, size_t errStrSize, const char *message) |
// MFSMDBCheckCore calls this routine to copy message into the user |
// supplied string buffer, if any. |
{ |
if (errStr != NULL) { |
strncpy(errStr, message, errStrSize); // no strlcpy in the kernel |
errStr[errStrSize - 1] = 0; // grrr |
} |
} |
static int MFSMDBCheckCore( |
const void * mdbBlockPtr, |
uint64_t containerBlockCount, |
size_t * mdbAndVABMSizeInBytesPtr, |
uint16_t * directoryStartBlockPtr, |
uint16_t * directoryBlockCountPtr, |
uint16_t * allocationBlocksStartBlockPtr, |
uint32_t * allocationBlockSizeInBytesPtr, |
char * errStr, |
size_t errStrSize |
) |
// This routine is the guts of both MFSMDBCheck and MFSMDBGetError. By rolling |
// both of these functions into a common routine, I can keep the error string |
// close to the validity check that generates it. |
// |
// All pointer parameters except mdbBlockPtr may be NULL. errStrSize may be |
// 0 if errStr is NULL. |
{ |
int err; |
const MFSMasterDirectoryBlock * mdbPtr; |
uint16_t directoryStartBlock; |
uint16_t directoryBlockCount; |
uint16_t allocationBlockCount; |
uint16_t allocationBlocksStartBlock; |
uint32_t allocationBlockSizeInBytes; |
// Parameter asserts. |
assert(mdbBlockPtr != NULL); |
assert( (errStrSize != 0) || (errStr == NULL) ); |
// Quick sanity check of the MDB and directory entry structures. These should |
// be compile-time asserts, but there is no handy compile-time assert macro available |
// to both user and kernel code. |
assert(sizeof(MFSMasterDirectoryBlock) == 64); |
assert(sizeof(MFSDirectoryRecord) == 306); // includes maximal length name |
assert(kMFSDirectoryRecordFixedSize == 51); |
mdbPtr = (const MFSMasterDirectoryBlock *) mdbBlockPtr; |
// Check the sigWord. |
err = 0; |
if ( OSSwapBigToHostInt16(mdbPtr->sigWord) != kMFSSigWord ) { |
SetErrorString(errStr, errStrSize, "incorrect signature"); |
err = EINVAL; |
} |
// Check that the MDB is compatible with the container block count. |
if (err == 0) { |
directoryStartBlock = OSSwapBigToHostInt16(mdbPtr->directoryStartBlock); |
directoryBlockCount = OSSwapBigToHostInt16(mdbPtr->directoryBlockCount); |
allocationBlockCount = OSSwapBigToHostInt16(mdbPtr->allocationBlockCount); |
allocationBlocksStartBlock = OSSwapBigToHostInt16(mdbPtr->allocationBlocksStartBlock); |
allocationBlockSizeInBytes = OSSwapBigToHostInt32(mdbPtr->allocationBlockSizeInBytes); |
} |
if ( (err == 0) && ( ((uint64_t) kMFSMDBBlock) >= containerBlockCount) ) { |
SetErrorString(errStr, errStrSize, "MDB falls outside of container"); |
err = EINVAL; |
} |
if ( (err == 0) && (directoryBlockCount == 0) ) { |
SetErrorString(errStr, errStrSize, "no directory blocks"); |
err = EINVAL; |
} |
if ( (err == 0) && ( ((uint64_t) directoryStartBlock) >= containerBlockCount) ) { |
SetErrorString(errStr, errStrSize, "directory starts outside of container"); |
err = EINVAL; |
} |
if ( (err == 0) && ( (((uint64_t) directoryStartBlock) + directoryBlockCount) >= containerBlockCount) ) { |
SetErrorString(errStr, errStrSize, "directory ends outside of container"); |
err = EINVAL; |
} |
if ( (err == 0) && ( ((uint64_t) allocationBlockCount) >= containerBlockCount) ) { |
SetErrorString(errStr, errStrSize, "more allocation blocks that container blocks"); |
// This assumes that each allocation block is at least one disk block. |
err = EINVAL; |
} |
if ( (err == 0) && ( ((uint64_t) allocationBlocksStartBlock) >= containerBlockCount) ) { |
SetErrorString(errStr, errStrSize, "allocation blocks start outside of container"); |
// This assumes that each allocation block is at least one disk block, which |
// is implied by IM II's statement that the allocation block must be an even |
// multiple of the block size. |
err = EINVAL; |
} |
// The following assumes that nextFileNumber doesn't wrap, which is a pretty |
// safe assumption for MFS (although later on, for HFS, we had to allow for |
// wrapping). |
if ( (err == 0) && (OSSwapBigToHostInt32(mdbPtr->nextFileNumber) <= OSSwapBigToHostInt16(mdbPtr->fileCount)) ) { |
SetErrorString(errStr, errStrSize, "next file number is not greater than file count"); |
err = EINVAL; |
} |
// Can't have more free allocation blocks than we have allocation blocks. |
if ( (err == 0) && (OSSwapBigToHostInt16(mdbPtr->freeAllocationBlockCount) > allocationBlockCount) ) { |
SetErrorString(errStr, errStrSize, "more free allocation blocks than allocation blocks"); |
err = EINVAL; |
} |
// As we're about to divide by allocationBlockSizeInBytes, let's check for zero first. |
// And while we're at it, make sure it's a power of two. |
if ( (err == 0) && (allocationBlockSizeInBytes == 0) ) { |
SetErrorString(errStr, errStrSize, "allocation block size is zero"); |
err = EINVAL; |
} |
if ( (err == 0) && ( (allocationBlockSizeInBytes & (allocationBlockSizeInBytes - 1)) != 0) ) { |
SetErrorString(errStr, errStrSize, "allocation block size is not a power of two"); |
err = EINVAL; |
} |
// Clump size must be a multiple of the allocation block size, per IM II-122. |
if ( (err == 0) && ((OSSwapBigToHostInt32(mdbPtr->clumpSizeInBytes) % allocationBlockSizeInBytes) != 0) ) { |
SetErrorString(errStr, errStrSize, "clump size is not an even multiple of the allocation block size"); |
err = EINVAL; |
} |
// We won't deal well with an empty volume name, or with names longer than the |
// space allocated for them in the MDB. |
if ( (err == 0) && (mdbPtr->nameLength == 0) ) { |
SetErrorString(errStr, errStrSize, "volume name is empty"); |
err = EINVAL; |
} |
if ( (err == 0) && (mdbPtr->nameLength > 27) ) { |
SetErrorString(errStr, errStrSize, "volume name too long"); |
err = EINVAL; |
} |
if (err == 0) { |
// When calculating how many bytes make up the VABM, consider the following: |
// |
// o (allocationBlockCount + 1) / 2 is the number of allocation block /pairs/ |
// o each pair needs three bytes |
if (mdbAndVABMSizeInBytesPtr != NULL) { |
*mdbAndVABMSizeInBytesPtr = sizeof(MFSMasterDirectoryBlock) |
+ (OSSwapBigToHostInt16(mdbPtr->allocationBlockCount) + 1) / 2 * 3; |
} |
if (directoryStartBlockPtr != NULL) { |
*directoryStartBlockPtr = directoryStartBlock; |
} |
if (directoryBlockCountPtr != NULL) { |
*directoryBlockCountPtr = directoryBlockCount; |
} |
if (allocationBlocksStartBlockPtr != NULL) { |
*allocationBlocksStartBlockPtr = allocationBlocksStartBlock; |
} |
if (allocationBlockSizeInBytesPtr != NULL) { |
*allocationBlockSizeInBytesPtr = allocationBlockSizeInBytes; |
} |
} |
return err; |
} |
extern int MFSMDBCheck( |
const void * mdbBlockPtr, |
uint64_t containerBlockCount, |
size_t * mdbAndVABMSizeInBytesPtr, |
uint16_t * directoryStartBlockPtr, |
uint16_t * directoryBlockCountPtr, |
uint16_t * allocationBlocksStartBlockPtr, |
uint32_t * allocationBlockSizeInBytesPtr |
) |
// See comments in header. |
{ |
return MFSMDBCheckCore( |
mdbBlockPtr, |
containerBlockCount, |
mdbAndVABMSizeInBytesPtr, |
directoryStartBlockPtr, |
directoryBlockCountPtr, |
allocationBlocksStartBlockPtr, |
allocationBlockSizeInBytesPtr, |
NULL, |
0 |
); |
} |
extern void MFSMDBGetError( |
const void * mdbBlockPtr, |
uint64_t containerBlockCount, |
char * errStr, |
size_t errStrSize |
) |
// See comments in header. |
{ |
int junk; |
assert(errStr != NULL); |
assert(errStrSize > 0); |
errStr[0] = 0; |
junk = MFSMDBCheckCore( |
mdbBlockPtr, |
containerBlockCount, |
NULL, |
NULL, |
NULL, |
NULL, |
NULL, |
errStr, |
errStrSize |
); |
assert(junk == EINVAL); // If MFSMDBCheck didn't fail, why are you calling us!?! |
} |
#if MACH_ASSERT |
static boolean_t MFSMDBValid(const MFSMasterDirectoryBlock *mdbPtr) |
// Does a basic check to see if mdbPtr is valid. |
{ |
assert(mdbPtr != NULL); |
assert( OSSwapBigToHostInt16(mdbPtr->sigWord) == kMFSSigWord ); |
return TRUE; |
} |
#endif |
static void MFSInitGetAttrListGoop(struct vfs_attr *attr) |
// Sets up the f_capabilities and f_attributes of attr. This is sufficiently |
// long winded that I pulled it out MFSMDBGetAttr to keep that code easy to read. |
{ |
assert(attr != NULL); |
attr->f_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] = 0 |
| VOL_CAP_FMT_PERSISTENTOBJECTIDS |
// | VOL_CAP_FMT_SYMBOLICLINKS |
// | VOL_CAP_FMT_HARDLINKS |
// | VOL_CAP_FMT_JOURNAL |
// | VOL_CAP_FMT_JOURNAL_ACTIVE |
// | VOL_CAP_FMT_NO_ROOT_TIMES |
// | VOL_CAP_FMT_SPARSE_FILES |
// | VOL_CAP_FMT_ZERO_RUNS |
// | VOL_CAP_FMT_CASE_SENSITIVE |
| VOL_CAP_FMT_CASE_PRESERVING |
| VOL_CAP_FMT_FAST_STATFS |
// | VOL_CAP_FMT_2TB_FILESIZE |
; |
attr->f_capabilities.valid[VOL_CAPABILITIES_FORMAT] = 0 |
| VOL_CAP_FMT_PERSISTENTOBJECTIDS |
| VOL_CAP_FMT_SYMBOLICLINKS |
| VOL_CAP_FMT_HARDLINKS |
| VOL_CAP_FMT_JOURNAL |
| VOL_CAP_FMT_JOURNAL_ACTIVE |
| VOL_CAP_FMT_NO_ROOT_TIMES |
| VOL_CAP_FMT_SPARSE_FILES |
| VOL_CAP_FMT_ZERO_RUNS |
| VOL_CAP_FMT_CASE_SENSITIVE |
| VOL_CAP_FMT_CASE_PRESERVING |
| VOL_CAP_FMT_FAST_STATFS |
| VOL_CAP_FMT_2TB_FILESIZE |
; |
attr->f_capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] = 0 |
// | VOL_CAP_INT_SEARCHFS |
| VOL_CAP_INT_ATTRLIST |
// | VOL_CAP_INT_NFSEXPORT |
// | VOL_CAP_INT_READDIRATTR |
// | VOL_CAP_INT_EXCHANGEDATA |
// | VOL_CAP_INT_COPYFILE |
// | VOL_CAP_INT_ALLOCATE |
// | VOL_CAP_INT_VOL_RENAME |
// | VOL_CAP_INT_ADVLOCK |
// | VOL_CAP_INT_FLOCK |
// | VOL_CAP_INT_EXTENDED_SECURITY |
// | VOL_CAP_INT_USERACCESS |
; |
attr->f_capabilities.valid[VOL_CAPABILITIES_INTERFACES] = 0 |
| VOL_CAP_INT_SEARCHFS |
| VOL_CAP_INT_ATTRLIST |
| VOL_CAP_INT_NFSEXPORT |
| VOL_CAP_INT_READDIRATTR |
| VOL_CAP_INT_EXCHANGEDATA |
| VOL_CAP_INT_COPYFILE |
| VOL_CAP_INT_ALLOCATE |
| VOL_CAP_INT_VOL_RENAME |
| VOL_CAP_INT_ADVLOCK |
| VOL_CAP_INT_FLOCK |
| VOL_CAP_INT_EXTENDED_SECURITY |
| VOL_CAP_INT_USERACCESS |
; |
attr->f_capabilities.capabilities[VOL_CAPABILITIES_RESERVED1] = 0; |
attr->f_capabilities.valid[VOL_CAPABILITIES_RESERVED1] = 0; |
attr->f_capabilities.capabilities[VOL_CAPABILITIES_RESERVED2] = 0; |
attr->f_capabilities.valid[VOL_CAPABILITIES_RESERVED2] = 0; |
VFSATTR_SET_SUPPORTED(attr, f_capabilities); |
attr->f_attributes.validattr.commonattr = 0 |
| ATTR_CMN_NAME |
| ATTR_CMN_DEVID |
| ATTR_CMN_FSID |
| ATTR_CMN_OBJTYPE |
// | ATTR_CMN_OBJTAG |
| ATTR_CMN_OBJID |
| ATTR_CMN_OBJPERMANENTID |
| ATTR_CMN_PAROBJID |
// | ATTR_CMN_SCRIPT |
| ATTR_CMN_CRTIME |
| ATTR_CMN_MODTIME |
// | ATTR_CMN_CHGTIME |
// | ATTR_CMN_ACCTIME |
| ATTR_CMN_BKUPTIME |
| ATTR_CMN_FNDRINFO |
| ATTR_CMN_OWNERID |
| ATTR_CMN_GRPID |
| ATTR_CMN_ACCESSMASK |
| ATTR_CMN_FLAGS |
// | ATTR_CMN_USERACCESS |
// | ATTR_CMN_EXTENDED_SECURITY |
// | ATTR_CMN_UUID |
// | ATTR_CMN_GRPUUID |
; |
attr->f_attributes.validattr.volattr = 0 |
| ATTR_VOL_FSTYPE |
| ATTR_VOL_SIGNATURE |
| ATTR_VOL_SIZE |
| ATTR_VOL_SPACEFREE |
| ATTR_VOL_SPACEAVAIL |
| ATTR_VOL_MINALLOCATION |
| ATTR_VOL_ALLOCATIONCLUMP |
| ATTR_VOL_IOBLOCKSIZE |
| ATTR_VOL_OBJCOUNT |
| ATTR_VOL_FILECOUNT |
| ATTR_VOL_DIRCOUNT |
| ATTR_VOL_MAXOBJCOUNT |
| ATTR_VOL_MOUNTPOINT |
| ATTR_VOL_NAME |
| ATTR_VOL_MOUNTFLAGS |
| ATTR_VOL_MOUNTEDDEVICE |
| ATTR_VOL_ENCODINGSUSED |
| ATTR_VOL_CAPABILITIES |
| ATTR_VOL_ATTRIBUTES |
; |
attr->f_attributes.validattr.dirattr = 0 |
| ATTR_DIR_LINKCOUNT |
| ATTR_DIR_ENTRYCOUNT |
// | ATTR_DIR_MOUNTSTATUS |
; |
attr->f_attributes.validattr.fileattr = 0 |
| ATTR_FILE_LINKCOUNT |
| ATTR_FILE_TOTALSIZE |
| ATTR_FILE_ALLOCSIZE |
| ATTR_FILE_IOBLOCKSIZE |
| ATTR_FILE_DEVTYPE |
// | ATTR_FILE_FORKCOUNT |
// | ATTR_FILE_FORKLIST |
| ATTR_FILE_DATALENGTH |
| ATTR_FILE_DATAALLOCSIZE |
| ATTR_FILE_RSRCLENGTH |
| ATTR_FILE_RSRCALLOCSIZE |
; |
attr->f_attributes.validattr.forkattr = 0; |
// All attributes that we do support, we support natively. |
attr->f_attributes.nativeattr.commonattr = attr->f_attributes.validattr.commonattr; |
attr->f_attributes.nativeattr.volattr = attr->f_attributes.validattr.volattr; |
attr->f_attributes.nativeattr.dirattr = attr->f_attributes.validattr.dirattr; |
attr->f_attributes.nativeattr.fileattr = attr->f_attributes.validattr.fileattr; |
attr->f_attributes.nativeattr.forkattr = attr->f_attributes.validattr.forkattr; |
VFSATTR_SET_SUPPORTED(attr, f_attributes); |
} |
// When I need to return a time which MFS doesn't have available, I return the |
// beginning of time. It's hand to have this around as a structure. |
static const struct timespec kZeroTime = {0, 0}; |
extern int MFSMDBGetAttr( |
const void * mdbBlockPtr, |
struct vfs_attr * attr |
) |
// See comments in header. |
{ |
size_t junkSize; |
const MFSMasterDirectoryBlock * mdbPtr; |
mdbPtr = (const MFSMasterDirectoryBlock *) mdbBlockPtr; |
assert( MFSMDBValid(mdbPtr) ); |
VFSATTR_RETURN(attr, f_objcount, OSSwapBigToHostInt16(mdbPtr->fileCount) + 1); // +1 for root directory |
VFSATTR_RETURN(attr, f_filecount, OSSwapBigToHostInt16(mdbPtr->fileCount)); |
VFSATTR_RETURN(attr, f_dircount, 1); |
VFSATTR_RETURN(attr, f_maxobjcount, ((512 / kMFSDirectoryRecordMinimumSize) * OSSwapBigToHostInt16(mdbPtr->directoryBlockCount)) + 1); |
// +1 for root directory |
VFSATTR_RETURN(attr, f_bsize, OSSwapBigToHostInt32(mdbPtr->allocationBlockSizeInBytes)); |
VFSATTR_RETURN(attr, f_iosize, OSSwapBigToHostInt32(mdbPtr->allocationBlockSizeInBytes)); |
VFSATTR_RETURN(attr, f_blocks, OSSwapBigToHostInt16(mdbPtr->allocationBlockCount)); |
VFSATTR_RETURN(attr, f_bfree, OSSwapBigToHostInt16(mdbPtr->freeAllocationBlockCount)); |
VFSATTR_RETURN(attr, f_bavail, OSSwapBigToHostInt16(mdbPtr->freeAllocationBlockCount)); |
VFSATTR_RETURN(attr, f_bused, attr->f_blocks - attr->f_bfree); |
VFSATTR_RETURN(attr, f_files, OSSwapBigToHostInt16(mdbPtr->fileCount)); |
VFSATTR_RETURN(attr, f_ffree, attr->f_maxobjcount - attr->f_files); |
// VFSATTR_RETURN(attr, f_fsid, x); // leave this up to higher layer software |
// VFSATTR_RETURN(attr, f_owner, x); // ditto |
MFSInitGetAttrListGoop(attr); |
// See MFSDirectoryEntryGetAttr for an important note about time attributes. |
VFSATTR_RETURN(attr, f_create_time, MFSDateTimeToTimeSpec(OSSwapBigToHostInt32(mdbPtr->creationDate))); |
VFSATTR_RETURN(attr, f_modify_time, attr->f_create_time); |
VFSATTR_RETURN(attr, f_access_time, kZeroTime); |
VFSATTR_RETURN(attr, f_backup_time, MFSDateTimeToTimeSpec(OSSwapBigToHostInt32(mdbPtr->backupDate))); |
VFSATTR_RETURN(attr, f_fssubtype, 0); |
if ( VFSATTR_IS_ACTIVE(attr, f_vol_name) ) { |
// Maximum volume name length is 27 characters; with a maximum UTF-8 expansion |
// of 3x, this yields 81, for a buffer size of 82, which is way less than MAXPATHLEN. |
assert( ((27 * kMacRomanToUTF8Expansion) + 1) <= MAXPATHLEN ); |
junkSize = MFSNameToUTF8(&mdbPtr->nameLength, attr->f_vol_name, MAXPATHLEN); |
assert(junkSize < MAXPATHLEN); |
VFSATTR_SET_SUPPORTED(attr, f_vol_name); |
} |
VFSATTR_RETURN(attr, f_signature, OSSwapBigToHostInt16(mdbPtr->sigWord)); |
VFSATTR_RETURN(attr, f_carbon_fsid, 0); // MFS shares this with HFS |
return 0; |
} |
// Special allocation block numbers: |
enum { |
// An allocation block number of zero is used, in a directory entry, to indicate that |
// a fork has no allocation blocks. |
kMFSEmptyAllocationBlock = 0, |
// An allocation block number of one is used, in the VABM, to indicate the end of the |
// allocation block chain. |
kMFSLastAllocationBlock = 1, |
// To avoid using the special values (defined in the header), the VABM is indexed by |
// allocation block - kMFSFirstAllocationBlock. |
kMFSFirstAllocationBlock = 2, |
// Because 0x0FFF is used by kMFSDirectoryAllocationBlock, the maximum valid allocation |
// block number is 0x0FFE. |
kMFSMaximumAllocationBlock = 0x0FFE, |
// It is legal for the directory to be within the space managed by the VABM (althoug |
// I've never seen an MFS disk constructed this way though). Still, in that case, |
// the directory blocks must be marked with a special value to avoid them being |
// used for anything else. |
kMFSDirectoryAllocationBlock = 0x0FFF |
}; |
static uint16_t MFSVABMNextAllocationBlock(const void *mdbAndVABMPtr, uint16_t allocationBlock) |
// Looks up allocation block in the VABM and returns the next allocation block. |
// The result can might be kMFSLastAllocationBlock. See the pre- and post-condition |
// asserts for the specifics. |
{ |
uint16_t result; |
const MFSMasterDirectoryBlock * mdbPtr; |
const uint8_t * vabmBase; |
size_t byteIndex; |
mdbPtr = (const MFSMasterDirectoryBlock *) mdbAndVABMPtr; |
assert( MFSMDBValid(mdbPtr) ); |
assert(allocationBlock != kMFSEmptyAllocationBlock); |
assert(allocationBlock != kMFSLastAllocationBlock); |
assert(allocationBlock <= kMFSMaximumAllocationBlock); |
assert(allocationBlock < (kMFSFirstAllocationBlock + OSSwapBigToHostInt16(mdbPtr->allocationBlockCount))); |
// VABM starts immediately after the MDB. |
vabmBase = (uint8_t *) (&mdbPtr[1]); |
// Remember that the VABM is a packed array of 12 bit entries, |
// which is a real pain to access from any language! |
// We first calculate the index of the first byte of the |
// VABM entry... |
byteIndex = (allocationBlock - kMFSFirstAllocationBlock) * 3 / 2; |
// ...then we do the bit shuffling depending on whether |
// allocationBlock is even or odd. |
if ( ((allocationBlock - kMFSFirstAllocationBlock) % 2) == 0 ) { |
result = (vabmBase[byteIndex] << 4) | ((vabmBase[byteIndex + 1] >> 4) & 0x0F); |
} else { |
result = ((vabmBase[byteIndex] & 0x0F) << 8) | vabmBase[byteIndex + 1]; |
} |
assert(result != kMFSEmptyAllocationBlock); |
assert(result < kMFSMaximumAllocationBlock); |
assert(result < (kMFSFirstAllocationBlock + OSSwapBigToHostInt16(mdbPtr->allocationBlockCount))); |
return result; |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** Directory Entry Routines |
extern int MFSDirectoryBlockIterate( |
const void * directoryBlockPtr, |
size_t directoryBlockSizeInBytes, |
size_t * dirOffsetPtr, |
struct vnode_attr * attr |
) |
// See comments in header. |
{ |
int err; |
size_t dirOffset; |
size_t dirOffsetInitial; |
assert(directoryBlockPtr != NULL); |
assert(directoryBlockSizeInBytes > kMFSDirectoryRecordFixedSize); |
assert(dirOffsetPtr != NULL); |
assert( (*dirOffsetPtr == kMFSDirectoryBlockIterateFromStart) || (*dirOffsetPtr < directoryBlockSizeInBytes) ); |
// attr can be NULL |
dirOffset = *dirOffsetPtr; |
dirOffsetInitial = dirOffset; |
// Calculate the dirOffset of the next directory record based on the offset passed in. |
if (dirOffset == kMFSDirectoryBlockIterateFromStart) { |
dirOffset = 0; |
} else { |
const MFSDirectoryRecord * lastDirRec; |
lastDirRec = ((const MFSDirectoryRecord *) (((const char *) directoryBlockPtr) + dirOffset)); |
// lastDirRec falls within the directory block |
assert( ((const char *) lastDirRec) >= ((const char *) directoryBlockPtr) ); |
assert( ((const char *) lastDirRec) < (((const char *) directoryBlockPtr) + directoryBlockSizeInBytes) ); |
// lastDirRec->nameLength falls within the directory block |
assert( ((const char *) &lastDirRec->nameLength) < (((const char *) directoryBlockPtr) + directoryBlockSizeInBytes) ); |
// The attributes field should be either 0x80 (allocated and not locked) or |
// 0x81 (allocated and locked). |
assert( lastDirRec->attributes & kMFSDirectoryRecordAllocatedAttr ); |
assert( (lastDirRec->attributes & kMFSDirectoryRecordReservedAttr) == 0); |
// Bump dirOffset by the size of the previous entry, including the pad byte if necessary. |
dirOffset += kMFSDirectoryRecordFixedSize + lastDirRec->nameLength; |
if ((dirOffset % 2) == 1) { |
dirOffset += 1; |
} |
} |
// Check that the result dirOffset is a valid directory record. See the comment in |
// MFSDirectoryBlockCheckDirOffset for a discussion of why I consider |
// dirOffset == directoryBlockSizeInBytes to be legal. |
err = 0; |
if (dirOffset >= directoryBlockSizeInBytes) { |
assert(dirOffset == directoryBlockSizeInBytes); |
err = ENOENT; |
} |
if (err == 0) { |
const MFSDirectoryRecord * thisDirRec; |
thisDirRec = ((const MFSDirectoryRecord *) (((const char *) directoryBlockPtr) + dirOffset)); |
// thisDirRec falls within the directory block |
assert( ((const char *) thisDirRec) >= ((const char *) directoryBlockPtr) ); |
assert( ((const char *) thisDirRec) < (((const char *) directoryBlockPtr) + directoryBlockSizeInBytes) ); |
if (thisDirRec->attributes == 0) { |
err = ENOENT; |
} else { |
// thisDirRec->nameLength falls within the directory block |
assert( ((const char *) &thisDirRec->nameLength) < (((const char *) directoryBlockPtr) + directoryBlockSizeInBytes) ); |
// entire name falls within the directory block |
assert( (const char *) (thisDirRec->name + thisDirRec->nameLength) <= (((const char *) directoryBlockPtr) + directoryBlockSizeInBytes) ); |
// The attributes field should be either 0x80 (allocated and not locked) or |
// 0x81 (allocated and locked). |
assert( thisDirRec->attributes & kMFSDirectoryRecordAllocatedAttr ); |
assert( (thisDirRec->attributes & kMFSDirectoryRecordReservedAttr) == 0); |
if (attr != NULL) { |
err = MFSDirectoryEntryGetAttr(directoryBlockPtr, dirOffset, attr); |
} |
} |
} |
if (err == 0) { |
*dirOffsetPtr = dirOffset; |
} |
assert( (*dirOffsetPtr == kMFSDirectoryBlockIterateFromStart) || (*dirOffsetPtr < directoryBlockSizeInBytes) ); |
assert( (err != 0) == (*dirOffsetPtr == dirOffsetInitial) ); // if we errored, *dirOffsetPtr must be unchanged |
return err; |
} |
extern int MFSDirectoryBlockCheckDirOffset( |
const void * directoryBlockPtr, |
size_t directoryBlockSizeInBytes, |
size_t candidateDirOffset |
) |
// See comments in header. |
{ |
int err; |
size_t dirOffset; |
const MFSDirectoryRecord * thisDirRec; |
assert(directoryBlockPtr != NULL); |
assert(directoryBlockSizeInBytes > kMFSDirectoryRecordFixedSize); |
assert(candidateDirOffset != kMFSDirectoryBlockIterateFromStart); // just don't call us in this case |
assert(candidateDirOffset < directoryBlockSizeInBytes); // ditto |
// Start at the beginning of the block. |
dirOffset = 0; |
do { |
// If this entry isn't valid, we're done. |
thisDirRec = ((const MFSDirectoryRecord *) (((const char *) directoryBlockPtr) + dirOffset)); |
if (thisDirRec->attributes == 0) { |
err = EINVAL; // hit end of valid directory entries |
break; |
} |
// Check that this directory entry is reasonable. |
assert( thisDirRec->attributes & kMFSDirectoryRecordAllocatedAttr ); |
assert( (thisDirRec->attributes & kMFSDirectoryRecordReservedAttr) == 0); |
assert( ((const char *) &thisDirRec->nameLength) < (((const char *) directoryBlockPtr) + directoryBlockSizeInBytes) ); |
assert( (const char *) (thisDirRec->name + thisDirRec->nameLength) <= (((const char *) directoryBlockPtr) + directoryBlockSizeInBytes) ); |
// We have a valid entry; if it's offset is the one we're looking for, we're done. |
if (dirOffset == candidateDirOffset) { |
err = 0; |
break; |
} |
// Advance to the next directory entry. |
dirOffset += kMFSDirectoryRecordFixedSize + thisDirRec->nameLength; |
if ((dirOffset % 2) == 1) { |
dirOffset += 1; |
} |
// If we run off the end of the block, we're done. I don't know if an MFS |
// directory block has to end with an empty entry (that is, a 0), or whether |
// it's possible for the last entry to butt right up against the end of the |
// block. IM II is silent on the issue. I'm going to take a stance and |
// say that it's legal, but obviously only if you hit the /exact/ end of the block. |
if (dirOffset >= directoryBlockSizeInBytes ) { |
assert(dirOffset == directoryBlockSizeInBytes); |
err = EINVAL; |
break; |
} |
} while (TRUE); |
return err; |
} |
extern int MFSDirectoryBlockFindEntryByName( |
const void * directoryBlockPtr, |
size_t directoryBlockSizeInBytes, |
const char * utf8Name, |
size_t utf8NameLen, |
void * tempBuffer, |
size_t * dirOffsetPtr, |
struct vnode_attr * attr |
) |
// See comments in header. |
{ |
int err; |
size_t dirOffset; |
uint8_t * mfsNameUpper; |
assert(directoryBlockPtr != NULL); |
assert(directoryBlockSizeInBytes > kMFSDirectoryRecordFixedSize); |
assert(utf8Name != NULL); |
assert(tempBuffer != NULL); |
assert(dirOffsetPtr != NULL); |
// *dirOffsetPtr ignored on input |
// attr can be NULL |
// If this is the first time we've seen tempBuffer, its first byte will be 0. |
// In that case, use it to cache a copy of the uppercased MFS name associated |
// with utf8Name. This avoids the cost of the UTF-8 -> MacRoman -> upper case |
// if you're scanning multiple directory blocks. |
mfsNameUpper = (uint8_t *) tempBuffer; |
if (mfsNameUpper[0] != 0 ) { |
err = 0; |
} else { |
// Use tempBuffer + 256 as the tempory buffer for UTF8ToMFSName, and |
// tempBuffer + 0 as the place to store the resulting MFS name. |
err = UTF8ToMFSName(utf8Name, utf8NameLen, ((char *) tempBuffer) + 256, mfsNameUpper); |
if (err == 0) { |
MFSNameToUpper(mfsNameUpper); |
// Empty names are unacceptable, in general /and/ because we use the first |
// byte to determine if tempBuffer has been initialised yet. |
if (mfsNameUpper[0] == 0) { |
err = EINVAL; |
} |
} |
} |
// Iterate the directory block looking for the name. |
if (err == 0) { |
boolean_t found; |
dirOffset = kMFSDirectoryBlockIterateFromStart; |
found = FALSE; |
do { |
err = MFSDirectoryBlockIterate( |
directoryBlockPtr, |
directoryBlockSizeInBytes, |
&dirOffset, |
NULL |
); |
if (err == 0) { |
const MFSDirectoryRecord * dirRec; |
dirRec = ((const MFSDirectoryRecord *) (((const char *) directoryBlockPtr) + dirOffset)); |
found = MFSNameEqualToUpper(&dirRec->nameLength, mfsNameUpper); |
} |
} while ( (err == 0) && ! found); |
assert(found == (err == 0)); |
} |
// If we would it, return its offset (and attributes, if requested) to the caller. |
if (err == 0) { |
*dirOffsetPtr = dirOffset; |
if (attr != NULL) { |
err = MFSDirectoryEntryGetAttr(directoryBlockPtr, dirOffset, attr); |
} |
} |
assert( (err != 0) || (*dirOffsetPtr <= directoryBlockSizeInBytes) ); |
return err; |
} |
extern int MFSDirectoryEntryGetAttr( |
const void * directoryBlockPtr, |
size_t dirOffset, |
struct vnode_attr * attr |
) |
// See comments in header. |
{ |
const MFSDirectoryRecord * dirRec; |
size_t junkSize; |
assert(directoryBlockPtr != NULL); |
assert(attr != NULL); |
dirRec = ((const MFSDirectoryRecord *) (((const char *) directoryBlockPtr) + dirOffset)); |
VATTR_RETURN(attr, va_rdev, 0); |
VATTR_RETURN(attr, va_nlink, 1); |
VATTR_RETURN(attr, va_total_size, OSSwapBigToHostInt32(dirRec->dataLengthInBytes) + OSSwapBigToHostInt32(dirRec->rsrcLengthInBytes)); |
VATTR_RETURN(attr, va_total_alloc, OSSwapBigToHostInt32(dirRec->dataPhysicalLengthInBytes) + OSSwapBigToHostInt32(dirRec->rsrcPhysicalLengthInBytes)); |
VATTR_RETURN(attr, va_data_size, OSSwapBigToHostInt32(dirRec->dataLengthInBytes)); |
VATTR_RETURN(attr, va_data_alloc, OSSwapBigToHostInt32(dirRec->dataPhysicalLengthInBytes)); |
// VATTR_RETURN(attr, va_iosize, xxx); |
// VATTR_RETURN(attr, va_uid, xxx); |
// VATTR_RETURN(attr, va_gid, xxx); |
VATTR_RETURN(attr, va_mode, S_IFREG | S_IRUSR | S_IRGRP | S_IROTH); |
VATTR_RETURN(attr, va_flags, (dirRec->attributes & kMFSDirectoryRecordLockedAttr) ? (SF_IMMUTABLE | UF_IMMUTABLE) : 0); |
// We have to support /all/ of the times, otherwise File Manager |
// won't play with us. Specifically, File Manager calls getattrlist to |
// request these attributes, and the getattrlist implementation tests |
// our VATTR_RETURN values to see if we support them and, if we don't, |
// return an error to File Manager. This causes File Manager to get |
// very confused, and results in Very Bad Things (tm). |
// |
// So we claim to support everything and then just return a nice constant |
// value for those which we don't have meaningful data. |
VATTR_RETURN(attr, va_create_time, MFSDateTimeToTimeSpec(OSSwapBigToHostInt32(dirRec->creationDate))); |
VATTR_RETURN(attr, va_access_time, kZeroTime); |
VATTR_RETURN(attr, va_modify_time, MFSDateTimeToTimeSpec(OSSwapBigToHostInt32(dirRec->modificationDate))); |
attr->va_change_time = kZeroTime; // don't have to claim support for va_change_time because VFS sets it to va_modify_time |
VATTR_RETURN(attr, va_backup_time, kZeroTime); |
VATTR_RETURN(attr, va_fileid, OSSwapBigToHostInt32(dirRec->fileNumber) - 1 + kMFSFirstFileInodeName); |
// VATTR_RETURN(attr, va_linkid, xxx); |
VATTR_RETURN(attr, va_parentid, kMFSRootInodeNumber); |
// VATTR_RETURN(attr, va_fsid, xxx); |
// VATTR_RETURN(attr, va_filerev, xxx); |
// VATTR_RETURN(attr, va_gen, xxx); |
VATTR_RETURN(attr, va_encoding, 0); // 0 -> MacRoman |
VATTR_RETURN(attr, va_type, VREG); |
if ( VATTR_IS_ACTIVE(attr, va_name) ) { |
// Maximum volume name length is 255 characters; with a maximum UTF-8 expansion |
// of 3x, this yields 765, for a buffer size of 766, which is definitely less than |
// MAXPATHLEN. |
assert( ((255 * kMacRomanToUTF8Expansion) + 1) <= MAXPATHLEN ); |
junkSize = MFSNameToUTF8(&dirRec->nameLength, attr->va_name, MAXPATHLEN); |
assert(junkSize < MAXPATHLEN); |
VATTR_SET_SUPPORTED(attr, va_name); |
} |
// VATTR_RETURN(attr, va_uuuid, xxx); |
// VATTR_RETURN(attr, va_guuid, xxx); |
VATTR_RETURN(attr, va_nchildren, 0); |
return 0; |
} |
extern int MFSDirectoryEntryGetFinderInfo( |
const void * directoryBlockPtr, |
size_t dirOffset, |
void * finderInfoPtr |
) |
// See comments in header. |
{ |
const MFSDirectoryRecord * dirRec; |
assert(directoryBlockPtr != NULL); |
// nothing we can say about dirOffset, other than to use it to set up dirRec and then check dirRec itself |
dirRec = ((const MFSDirectoryRecord *) (((const char *) directoryBlockPtr) + dirOffset)); |
assert( dirRec->attributes & kMFSDirectoryRecordAllocatedAttr ); |
assert( (dirRec->attributes & kMFSDirectoryRecordReservedAttr) == 0); |
assert(finderInfoPtr != NULL); |
memcpy(finderInfoPtr, dirRec->finderInfo, sizeof(dirRec->finderInfo)); |
return 0; |
} |
extern int MFSDirectoryEntryGetForkInfo( |
const void * directoryBlockPtr, |
size_t dirOffset, |
size_t forkIndex, |
MFSForkInfo * forkInfo |
) |
// See comments in header. |
{ |
const MFSDirectoryRecord * dirRec; |
// Pre-conditions |
assert(directoryBlockPtr != NULL); |
// nothing we can say about dirOffset, other than to use it to set up dirRec and then check dirRec itself |
dirRec = ((const MFSDirectoryRecord *) (((const char *) directoryBlockPtr) + dirOffset)); |
assert( dirRec->attributes & kMFSDirectoryRecordAllocatedAttr ); |
assert( (dirRec->attributes & kMFSDirectoryRecordReservedAttr) == 0); |
assert( (forkIndex == 0) || (forkIndex == 1) ); |
assert(forkInfo != NULL); |
// Implementation |
if (forkIndex == 0) { |
forkInfo->firstAllocationBlock = OSSwapBigToHostInt16(dirRec->dataFirstAllocationBlock); |
forkInfo->lengthInBytes = OSSwapBigToHostInt32(dirRec->dataLengthInBytes); |
forkInfo->physicalLengthInBytes = OSSwapBigToHostInt32(dirRec->dataPhysicalLengthInBytes); |
} else { |
forkInfo->firstAllocationBlock = OSSwapBigToHostInt16(dirRec->rsrcFirstAllocationBlock); |
forkInfo->lengthInBytes = OSSwapBigToHostInt32(dirRec->rsrcLengthInBytes); |
forkInfo->physicalLengthInBytes = OSSwapBigToHostInt32(dirRec->rsrcPhysicalLengthInBytes); |
} |
assert( (forkInfo->firstAllocationBlock != 0) == (forkInfo->lengthInBytes != 0) ); |
assert( (forkInfo->firstAllocationBlock != 0) == (forkInfo->physicalLengthInBytes != 0) ); |
assert( forkInfo->lengthInBytes <= forkInfo->physicalLengthInBytes ); |
return 0; |
} |
///////////////////////////////////////////////////////////////////// |
#pragma mark ***** File Fork Routines |
extern int MFSForkGetExtent( |
const void * mdbAndVABMPtr, |
const MFSForkInfo * forkInfo, |
uint32_t forkOffsetInBytes, |
uint32_t * offsetFromFirstAllocationBlockInBytesPtr, |
uint32_t * contiguousPhysicalBytesPtr |
) |
// See comments in header. |
{ |
int err; |
const MFSMasterDirectoryBlock * mdbPtr; |
uint32_t allocationBlockSizeInBytes; |
uint16_t currentAllocationBlock; |
uint32_t currentForkOffsetInBytes; |
uint32_t contiguousPhysicalBytes; |
// Pre-conditions |
assert(mdbAndVABMPtr != NULL); |
mdbPtr = (const MFSMasterDirectoryBlock *) mdbAndVABMPtr; |
assert( MFSMDBValid(mdbPtr) ); |
assert(forkInfo != NULL); |
assert(forkInfo->firstAllocationBlock != kMFSLastAllocationBlock); |
assert(forkInfo->firstAllocationBlock <= kMFSMaximumAllocationBlock); |
assert(forkInfo->firstAllocationBlock < (kMFSFirstAllocationBlock + OSSwapBigToHostInt16(mdbPtr->allocationBlockCount))); |
// assert(forkOffsetInBytes <= forkInfo->lengthInBytes); // I'm going to allow you to request up to the physical fork length |
assert(forkOffsetInBytes <= forkInfo->physicalLengthInBytes); |
assert(forkInfo->lengthInBytes <= forkInfo->physicalLengthInBytes); |
allocationBlockSizeInBytes = OSSwapBigToHostInt32(mdbPtr->allocationBlockSizeInBytes); |
assert( (forkOffsetInBytes % allocationBlockSizeInBytes) == 0 ); |
assert(offsetFromFirstAllocationBlockInBytesPtr != NULL); |
assert(contiguousPhysicalBytesPtr != NULL); |
// Implementation |
if (forkInfo->firstAllocationBlock == 0) { |
assert(forkInfo->lengthInBytes == 0); |
assert(forkInfo->physicalLengthInBytes == 0); |
err = EINVAL; // asking for the on-disk extent of an empty fork is bogus |
} else { |
// Search for the allocation block that contains forkOffsetInBytes. |
currentAllocationBlock = forkInfo->firstAllocationBlock; |
currentForkOffsetInBytes = 0; |
do { |
assert(currentAllocationBlock != kMFSEmptyAllocationBlock); |
assert(currentAllocationBlock != kMFSLastAllocationBlock); |
assert(currentAllocationBlock <= kMFSMaximumAllocationBlock); |
assert(currentAllocationBlock < (kMFSFirstAllocationBlock + OSSwapBigToHostInt16(mdbPtr->allocationBlockCount))); |
if (forkOffsetInBytes < (currentForkOffsetInBytes + allocationBlockSizeInBytes)) { |
err = 0; |
} else { |
currentAllocationBlock = MFSVABMNextAllocationBlock(mdbPtr, currentAllocationBlock); |
if (currentAllocationBlock == kMFSLastAllocationBlock) { |
err = EPIPE; // that is, end of file |
} else { |
currentForkOffsetInBytes += allocationBlockSizeInBytes; |
err = EAGAIN; |
} |
} |
} while (err == EAGAIN); |
// If that succeeded, work out how many contiguous bytes there are. |
if (err == 0) { |
uint16_t currentContiguousAllocationBlock; |
uint16_t nextContiguousAllocationBlock; |
currentContiguousAllocationBlock = currentAllocationBlock; |
contiguousPhysicalBytes = allocationBlockSizeInBytes; |
do { |
assert(currentContiguousAllocationBlock != kMFSEmptyAllocationBlock); |
assert(currentContiguousAllocationBlock != kMFSLastAllocationBlock); |
assert(currentContiguousAllocationBlock <= kMFSMaximumAllocationBlock); |
assert(currentContiguousAllocationBlock < (kMFSFirstAllocationBlock + OSSwapBigToHostInt16(mdbPtr->allocationBlockCount))); |
nextContiguousAllocationBlock = MFSVABMNextAllocationBlock(mdbPtr, currentContiguousAllocationBlock); |
if (nextContiguousAllocationBlock == kMFSLastAllocationBlock) { |
err = 0; |
} else if (nextContiguousAllocationBlock != (currentContiguousAllocationBlock + 1)) { |
err = 0; |
} else { |
currentContiguousAllocationBlock = nextContiguousAllocationBlock; |
contiguousPhysicalBytes += allocationBlockSizeInBytes; |
err = EAGAIN; |
} |
} while (err == EAGAIN); |
} |
} |
if (err == 0) { |
assert(currentAllocationBlock != kMFSEmptyAllocationBlock); |
assert(currentAllocationBlock != kMFSLastAllocationBlock); |
assert(currentAllocationBlock <= kMFSMaximumAllocationBlock); |
assert(currentAllocationBlock < (kMFSFirstAllocationBlock + OSSwapBigToHostInt16(mdbPtr->allocationBlockCount))); |
*offsetFromFirstAllocationBlockInBytesPtr = (currentAllocationBlock - kMFSFirstAllocationBlock) * allocationBlockSizeInBytes; |
*contiguousPhysicalBytesPtr = contiguousPhysicalBytes; |
} |
// Post-conditions |
if (err == 0) { |
assert((*offsetFromFirstAllocationBlockInBytesPtr % allocationBlockSizeInBytes) == 0); |
assert((*contiguousPhysicalBytesPtr % allocationBlockSizeInBytes) == 0); |
} |
return err; |
} |
Copyright © 2006 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2006-11-09