1166 lines
33 KiB
C
1166 lines
33 KiB
C
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <hfs/hfsplus.h>
|
|
|
|
static inline void flipBSDInfo(HFSPlusBSDInfo* info) {
|
|
FLIPENDIAN(info->ownerID);
|
|
FLIPENDIAN(info->groupID);
|
|
FLIPENDIAN(info->fileMode);
|
|
FLIPENDIAN(info->special);
|
|
}
|
|
|
|
static inline void flipPoint(Point* point) {
|
|
FLIPENDIAN(point->v);
|
|
FLIPENDIAN(point->h);
|
|
}
|
|
|
|
static inline void flipRect(Rect* rect) {
|
|
FLIPENDIAN(rect->top);
|
|
FLIPENDIAN(rect->left);
|
|
FLIPENDIAN(rect->bottom);
|
|
FLIPENDIAN(rect->right);
|
|
}
|
|
|
|
static inline void flipFolderInfo(FolderInfo* info) {
|
|
flipRect(&info->windowBounds);
|
|
FLIPENDIAN(info->finderFlags);
|
|
flipPoint(&info->location);
|
|
}
|
|
|
|
static inline void flipExtendedFolderInfo(ExtendedFolderInfo* info) {
|
|
flipPoint(&info->scrollPosition);
|
|
FLIPENDIAN(info->extendedFinderFlags);
|
|
FLIPENDIAN(info->putAwayFolderID);
|
|
}
|
|
|
|
static inline void flipFileInfo(FileInfo* info) {
|
|
FLIPENDIAN(info->fileType);
|
|
FLIPENDIAN(info->fileCreator);
|
|
FLIPENDIAN(info->finderFlags);
|
|
flipPoint(&info->location);
|
|
}
|
|
|
|
static inline void flipExtendedFileInfo(ExtendedFileInfo* info) {
|
|
FLIPENDIAN(info->extendedFinderFlags);
|
|
FLIPENDIAN(info->putAwayFolderID);
|
|
}
|
|
|
|
void flipCatalogFolder(HFSPlusCatalogFolder* record) {
|
|
FLIPENDIAN(record->recordType);
|
|
FLIPENDIAN(record->flags);
|
|
FLIPENDIAN(record->valence);
|
|
FLIPENDIAN(record->folderID);
|
|
FLIPENDIAN(record->createDate);
|
|
FLIPENDIAN(record->contentModDate);
|
|
FLIPENDIAN(record->attributeModDate);
|
|
FLIPENDIAN(record->accessDate);
|
|
FLIPENDIAN(record->backupDate);
|
|
|
|
flipBSDInfo(&record->permissions);
|
|
flipFolderInfo(&record->userInfo);
|
|
flipExtendedFolderInfo(&record->finderInfo);
|
|
|
|
FLIPENDIAN(record->textEncoding);
|
|
FLIPENDIAN(record->folderCount);
|
|
}
|
|
|
|
void flipCatalogFile(HFSPlusCatalogFile* record) {
|
|
FLIPENDIAN(record->recordType);
|
|
FLIPENDIAN(record->flags);
|
|
FLIPENDIAN(record->fileID);
|
|
FLIPENDIAN(record->createDate);
|
|
FLIPENDIAN(record->contentModDate);
|
|
FLIPENDIAN(record->attributeModDate);
|
|
FLIPENDIAN(record->accessDate);
|
|
FLIPENDIAN(record->backupDate);
|
|
|
|
flipBSDInfo(&record->permissions);
|
|
flipFileInfo(&record->userInfo);
|
|
flipExtendedFileInfo(&record->finderInfo);
|
|
|
|
FLIPENDIAN(record->textEncoding);
|
|
|
|
flipForkData(&record->dataFork);
|
|
flipForkData(&record->resourceFork);
|
|
}
|
|
|
|
void flipCatalogThread(HFSPlusCatalogThread* record, int out) {
|
|
int i;
|
|
int nameLength;
|
|
|
|
FLIPENDIAN(record->recordType);
|
|
FLIPENDIAN(record->parentID);
|
|
if(out) {
|
|
nameLength = record->nodeName.length;
|
|
FLIPENDIAN(record->nodeName.length);
|
|
} else {
|
|
FLIPENDIAN(record->nodeName.length);
|
|
nameLength = record->nodeName.length;
|
|
}
|
|
|
|
for(i = 0; i < nameLength; i++) {
|
|
if(out) {
|
|
if(record->nodeName.unicode[i] == ':') {
|
|
record->nodeName.unicode[i] = '/';
|
|
}
|
|
FLIPENDIAN(record->nodeName.unicode[i]);
|
|
} else {
|
|
FLIPENDIAN(record->nodeName.unicode[i]);
|
|
if(record->nodeName.unicode[i] == '/') {
|
|
record->nodeName.unicode[i] = ':';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define UNICODE_START (sizeof(uint16_t) + sizeof(HFSCatalogNodeID) + sizeof(uint16_t))
|
|
|
|
static void catalogKeyPrint(BTKey* toPrint) {
|
|
HFSPlusCatalogKey* key;
|
|
|
|
key = (HFSPlusCatalogKey*) toPrint;
|
|
|
|
printf("%d:", key->parentID);
|
|
printUnicode(&key->nodeName);
|
|
}
|
|
|
|
static int catalogCompare(BTKey* vLeft, BTKey* vRight) {
|
|
HFSPlusCatalogKey* left;
|
|
HFSPlusCatalogKey* right;
|
|
uint16_t i;
|
|
|
|
uint16_t cLeft;
|
|
uint16_t cRight;
|
|
|
|
left = (HFSPlusCatalogKey*) vLeft;
|
|
right =(HFSPlusCatalogKey*) vRight;
|
|
|
|
if(left->parentID < right->parentID) {
|
|
return -1;
|
|
} else if(left->parentID > right->parentID) {
|
|
return 1;
|
|
} else {
|
|
for(i = 0; i < left->nodeName.length; i++) {
|
|
if(i >= right->nodeName.length) {
|
|
return 1;
|
|
} else {
|
|
/* ugly hack to support weird : to / conversion on iPhone */
|
|
if(left->nodeName.unicode[i] == ':') {
|
|
cLeft = '/';
|
|
} else {
|
|
cLeft = left->nodeName.unicode[i] ;
|
|
}
|
|
|
|
if(right->nodeName.unicode[i] == ':') {
|
|
cRight = '/';
|
|
} else {
|
|
cRight = right->nodeName.unicode[i];
|
|
}
|
|
|
|
if(cLeft < cRight)
|
|
return -1;
|
|
else if(cLeft > cRight)
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if(i < right->nodeName.length) {
|
|
return -1;
|
|
} else {
|
|
/* do a safety check on key length. Otherwise, bad things may happen later on when we try to add or remove with this key */
|
|
/*if(left->keyLength == right->keyLength) {
|
|
return 0;
|
|
} else if(left->keyLength < right->keyLength) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}*/
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int catalogCompareCS(BTKey* vLeft, BTKey* vRight) {
|
|
HFSPlusCatalogKey* left;
|
|
HFSPlusCatalogKey* right;
|
|
|
|
left = (HFSPlusCatalogKey*) vLeft;
|
|
right =(HFSPlusCatalogKey*) vRight;
|
|
|
|
if(left->parentID < right->parentID) {
|
|
return -1;
|
|
} else if(left->parentID > right->parentID) {
|
|
return 1;
|
|
} else {
|
|
return FastUnicodeCompare(left->nodeName.unicode, left->nodeName.length, right->nodeName.unicode, right->nodeName.length);
|
|
}
|
|
}
|
|
|
|
static BTKey* catalogKeyRead(off_t offset, io_func* io) {
|
|
HFSPlusCatalogKey* key;
|
|
uint16_t i;
|
|
|
|
key = (HFSPlusCatalogKey*) malloc(sizeof(HFSPlusCatalogKey));
|
|
|
|
if(!READ(io, offset, UNICODE_START, key))
|
|
return NULL;
|
|
|
|
FLIPENDIAN(key->keyLength);
|
|
FLIPENDIAN(key->parentID);
|
|
FLIPENDIAN(key->nodeName.length);
|
|
|
|
if(!READ(io, offset + UNICODE_START, key->nodeName.length * sizeof(uint16_t), ((unsigned char *)key) + UNICODE_START))
|
|
return NULL;
|
|
|
|
for(i = 0; i < key->nodeName.length; i++) {
|
|
FLIPENDIAN(key->nodeName.unicode[i]);
|
|
if(key->nodeName.unicode[i] == '/') /* ugly hack that iPhone seems to do */
|
|
key->nodeName.unicode[i] = ':';
|
|
}
|
|
|
|
return (BTKey*)key;
|
|
}
|
|
|
|
static int catalogKeyWrite(off_t offset, BTKey* toWrite, io_func* io) {
|
|
HFSPlusCatalogKey* key;
|
|
uint16_t i;
|
|
uint16_t keyLength;
|
|
uint16_t nodeNameLength;
|
|
|
|
keyLength = toWrite->keyLength + sizeof(uint16_t);
|
|
key = (HFSPlusCatalogKey*) malloc(keyLength);
|
|
memcpy(key, toWrite, keyLength);
|
|
|
|
nodeNameLength = key->nodeName.length;
|
|
|
|
FLIPENDIAN(key->keyLength);
|
|
FLIPENDIAN(key->parentID);
|
|
FLIPENDIAN(key->nodeName.length);
|
|
|
|
for(i = 0; i < nodeNameLength; i++) {
|
|
if(key->nodeName.unicode[i] == ':') /* ugly hack that iPhone seems to do */
|
|
key->nodeName.unicode[i] = '/';
|
|
|
|
FLIPENDIAN(key->nodeName.unicode[i]);
|
|
}
|
|
|
|
if(!WRITE(io, offset, keyLength, key))
|
|
return FALSE;
|
|
|
|
free(key);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BTKey* catalogDataRead(off_t offset, io_func* io) {
|
|
int16_t recordType;
|
|
HFSPlusCatalogRecord* record;
|
|
uint16_t nameLength;
|
|
|
|
if(!READ(io, offset, sizeof(int16_t), &recordType))
|
|
return NULL;
|
|
|
|
FLIPENDIAN(recordType); fflush(stdout);
|
|
|
|
switch(recordType) {
|
|
case kHFSPlusFolderRecord:
|
|
record = (HFSPlusCatalogRecord*) malloc(sizeof(HFSPlusCatalogFolder));
|
|
if(!READ(io, offset, sizeof(HFSPlusCatalogFolder), record))
|
|
return NULL;
|
|
flipCatalogFolder((HFSPlusCatalogFolder*)record);
|
|
break;
|
|
|
|
case kHFSPlusFileRecord:
|
|
record = (HFSPlusCatalogRecord*) malloc(sizeof(HFSPlusCatalogFile));
|
|
if(!READ(io, offset, sizeof(HFSPlusCatalogFile), record))
|
|
return NULL;
|
|
flipCatalogFile((HFSPlusCatalogFile*)record);
|
|
break;
|
|
|
|
case kHFSPlusFolderThreadRecord:
|
|
case kHFSPlusFileThreadRecord:
|
|
record = (HFSPlusCatalogRecord*) malloc(sizeof(HFSPlusCatalogThread));
|
|
|
|
if(!READ(io, offset + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t), sizeof(uint16_t), &nameLength))
|
|
return NULL;
|
|
|
|
FLIPENDIAN(nameLength);
|
|
|
|
if(!READ(io, offset, sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint16_t) + (sizeof(uint16_t) * nameLength), record))
|
|
return NULL;
|
|
|
|
flipCatalogThread((HFSPlusCatalogThread*)record, FALSE);
|
|
break;
|
|
}
|
|
|
|
return (BTKey*)record;
|
|
}
|
|
|
|
void ASCIIToUnicode(const char* ascii, HFSUniStr255* unistr) {
|
|
int count;
|
|
|
|
count = 0;
|
|
while(ascii[count] != '\0') {
|
|
unistr->unicode[count] = ascii[count];
|
|
count++;
|
|
}
|
|
|
|
unistr->length = count;
|
|
}
|
|
|
|
HFSPlusCatalogRecord* getRecordByCNID(HFSCatalogNodeID CNID, Volume* volume) {
|
|
HFSPlusCatalogKey key;
|
|
HFSPlusCatalogThread* thread;
|
|
HFSPlusCatalogRecord* record;
|
|
int exact;
|
|
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
key.parentID = CNID;
|
|
key.nodeName.length = 0;
|
|
|
|
thread = (HFSPlusCatalogThread*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
|
|
if(thread == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if(exact == FALSE) {
|
|
free(thread);
|
|
return NULL;
|
|
}
|
|
|
|
key.parentID = thread->parentID;
|
|
key.nodeName = thread->nodeName;
|
|
|
|
free(thread);
|
|
|
|
record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
|
|
if(record == NULL || exact == FALSE)
|
|
return NULL;
|
|
else
|
|
return record;
|
|
}
|
|
|
|
CatalogRecordList* getFolderContents(HFSCatalogNodeID CNID, Volume* volume) {
|
|
BTree* tree;
|
|
HFSPlusCatalogThread* record;
|
|
HFSPlusCatalogKey key;
|
|
uint32_t nodeNumber;
|
|
int recordNumber;
|
|
|
|
BTNodeDescriptor* descriptor;
|
|
off_t recordOffset;
|
|
off_t recordDataOffset;
|
|
HFSPlusCatalogKey* currentKey;
|
|
|
|
CatalogRecordList* list;
|
|
CatalogRecordList* lastItem;
|
|
CatalogRecordList* item;
|
|
|
|
char pathBuffer[1024];
|
|
HFSPlusCatalogRecord* toReturn;
|
|
HFSPlusCatalogKey nkey;
|
|
int exact;
|
|
|
|
tree = volume->catalogTree;
|
|
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
key.parentID = CNID;
|
|
key.nodeName.length = 0;
|
|
|
|
list = NULL;
|
|
|
|
record = (HFSPlusCatalogThread*) search(tree, (BTKey*)(&key), NULL, &nodeNumber, &recordNumber);
|
|
|
|
if(record == NULL)
|
|
return NULL;
|
|
|
|
free(record);
|
|
|
|
++recordNumber;
|
|
|
|
while(nodeNumber != 0) {
|
|
descriptor = readBTNodeDescriptor(nodeNumber, tree);
|
|
|
|
while(recordNumber < descriptor->numRecords) {
|
|
recordOffset = getRecordOffset(recordNumber, nodeNumber, tree);
|
|
currentKey = (HFSPlusCatalogKey*) READ_KEY(tree, recordOffset, tree->io);
|
|
recordDataOffset = recordOffset + currentKey->keyLength + sizeof(currentKey->keyLength);
|
|
|
|
if(currentKey->parentID == CNID) {
|
|
item = (CatalogRecordList*) malloc(sizeof(CatalogRecordList));
|
|
item->name = currentKey->nodeName;
|
|
item->record = (HFSPlusCatalogRecord*) READ_DATA(tree, recordDataOffset, tree->io);
|
|
|
|
if(item->record->recordType == kHFSPlusFileRecord && (((HFSPlusCatalogFile*)item->record)->userInfo.fileType) == kHardLinkFileType) {
|
|
sprintf(pathBuffer, "iNode%d", ((HFSPlusCatalogFile*)item->record)->permissions.special.iNodeNum);
|
|
nkey.parentID = volume->metadataDir;
|
|
ASCIIToUnicode(pathBuffer, &nkey.nodeName);
|
|
nkey.keyLength = sizeof(nkey.parentID) + sizeof(nkey.nodeName.length) + (sizeof(uint16_t) * nkey.nodeName.length);
|
|
|
|
toReturn = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&nkey), &exact, NULL, NULL);
|
|
|
|
free(item->record);
|
|
item->record = toReturn;
|
|
}
|
|
item->next = NULL;
|
|
|
|
if(list == NULL) {
|
|
list = item;
|
|
} else {
|
|
lastItem->next = item;
|
|
}
|
|
|
|
lastItem = item;
|
|
free(currentKey);
|
|
} else {
|
|
free(currentKey);
|
|
free(descriptor);
|
|
return list;
|
|
}
|
|
|
|
recordNumber++;
|
|
}
|
|
|
|
nodeNumber = descriptor->fLink;
|
|
recordNumber = 0;
|
|
|
|
free(descriptor);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
void releaseCatalogRecordList(CatalogRecordList* list) {
|
|
CatalogRecordList* next;
|
|
while(list) {
|
|
next = list->next;
|
|
free(list->record);
|
|
free(list);
|
|
list = next;
|
|
}
|
|
}
|
|
|
|
HFSPlusCatalogRecord* getLinkTarget(HFSPlusCatalogRecord* record, HFSCatalogNodeID parentID, HFSPlusCatalogKey *key, Volume* volume) {
|
|
io_func* io;
|
|
char pathBuffer[1024];
|
|
HFSPlusCatalogRecord* toReturn;
|
|
HFSPlusCatalogKey nkey;
|
|
int exact;
|
|
|
|
if(record->recordType == kHFSPlusFileRecord && (((HFSPlusCatalogFile*)record)->permissions.fileMode & S_IFLNK) == S_IFLNK) {
|
|
io = openRawFile(((HFSPlusCatalogFile*)record)->fileID, &(((HFSPlusCatalogFile*)record)->dataFork), record, volume);
|
|
READ(io, 0, (((HFSPlusCatalogFile*)record)->dataFork).logicalSize, pathBuffer);
|
|
CLOSE(io);
|
|
pathBuffer[(((HFSPlusCatalogFile*)record)->dataFork).logicalSize] = '\0';
|
|
toReturn = getRecordFromPath3(pathBuffer, volume, NULL, key, TRUE, TRUE, parentID);
|
|
free(record);
|
|
return toReturn;
|
|
} else if(record->recordType == kHFSPlusFileRecord && (((HFSPlusCatalogFile*)record)->userInfo.fileType) == kHardLinkFileType) {
|
|
sprintf(pathBuffer, "iNode%d", ((HFSPlusCatalogFile*)record)->permissions.special.iNodeNum);
|
|
nkey.parentID = volume->metadataDir;
|
|
ASCIIToUnicode(pathBuffer, &nkey.nodeName);
|
|
nkey.keyLength = sizeof(nkey.parentID) + sizeof(nkey.nodeName.length) + (sizeof(uint16_t) * nkey.nodeName.length);
|
|
|
|
toReturn = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&nkey), &exact, NULL, NULL);
|
|
|
|
free(record);
|
|
|
|
return toReturn;
|
|
} else {
|
|
return record;
|
|
}
|
|
}
|
|
|
|
static const uint16_t METADATA_DIR[] = {0, 0, 0, 0, 'H', 'F', 'S', '+', ' ', 'P', 'r', 'i', 'v', 'a', 't', 'e', ' ', 'D', 'a', 't', 'a'};
|
|
|
|
HFSCatalogNodeID getMetadataDirectoryID(Volume* volume) {
|
|
HFSPlusCatalogKey key;
|
|
HFSPlusCatalogFolder* record;
|
|
int exact;
|
|
HFSCatalogNodeID id;
|
|
|
|
key.nodeName.length = sizeof(METADATA_DIR) / sizeof(uint16_t);
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length) + sizeof(METADATA_DIR);
|
|
key.parentID = kHFSRootFolderID;
|
|
memcpy(key.nodeName.unicode, METADATA_DIR, sizeof(METADATA_DIR));
|
|
|
|
record = (HFSPlusCatalogFolder*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
id = record->folderID;
|
|
|
|
free(record);
|
|
|
|
return id;
|
|
}
|
|
|
|
HFSPlusCatalogRecord* getRecordFromPath(const char* path, Volume* volume, char **name, HFSPlusCatalogKey* retKey) {
|
|
return getRecordFromPath2(path, volume, name, retKey, TRUE);
|
|
}
|
|
|
|
HFSPlusCatalogRecord* getRecordFromPath2(const char* path, Volume* volume, char **name, HFSPlusCatalogKey* retKey, char traverse) {
|
|
return getRecordFromPath3(path, volume, name, retKey, TRUE, TRUE, kHFSRootFolderID);
|
|
}
|
|
|
|
HFSPlusCatalogRecord* getRecordFromPath3(const char* path, Volume* volume, char **name, HFSPlusCatalogKey* retKey, char traverse, char returnLink, HFSCatalogNodeID parentID) {
|
|
HFSPlusCatalogKey key;
|
|
HFSPlusCatalogRecord* record;
|
|
|
|
char* origPath;
|
|
char* myPath;
|
|
char* word;
|
|
char* pathLimit;
|
|
|
|
uint32_t realParent;
|
|
|
|
int exact;
|
|
|
|
if(path[0] == '\0' || (path[0] == '/' && path[1] == '\0')) {
|
|
if(name != NULL)
|
|
*name = (char*)path;
|
|
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
key.parentID = kHFSRootFolderID;
|
|
key.nodeName.length = 0;
|
|
|
|
record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
key.parentID = ((HFSPlusCatalogThread*)record)->parentID;
|
|
key.nodeName = ((HFSPlusCatalogThread*)record)->nodeName;
|
|
|
|
free(record);
|
|
|
|
record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
return record;
|
|
}
|
|
|
|
myPath = strdup(path);
|
|
origPath = myPath;
|
|
|
|
record = NULL;
|
|
|
|
if(path[0] == '/') {
|
|
key.parentID = kHFSRootFolderID;
|
|
} else {
|
|
key.parentID = parentID;
|
|
}
|
|
|
|
pathLimit = myPath + strlen(myPath);
|
|
|
|
for(word = (char*)strtok(myPath, "/"); word && (word < pathLimit);
|
|
word = ((word + strlen(word) + 1) < pathLimit) ? (char*)strtok(word + strlen(word) + 1, "/") : NULL) {
|
|
|
|
if(name != NULL)
|
|
*name = (char*)(path + (word - origPath));
|
|
|
|
if(record != NULL) {
|
|
free(record);
|
|
record = NULL;
|
|
}
|
|
|
|
if(word[0] == '\0') {
|
|
continue;
|
|
}
|
|
|
|
ASCIIToUnicode(word, &key.nodeName);
|
|
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length) + (sizeof(uint16_t) * key.nodeName.length);
|
|
record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
|
|
if(record == NULL || exact == FALSE) {
|
|
free(origPath);
|
|
if(record != NULL) {
|
|
free(record);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
if(traverse) {
|
|
if(((word + strlen(word) + 1) < pathLimit) || returnLink) {
|
|
record = getLinkTarget(record, key.parentID, &key, volume);
|
|
if(record == NULL || exact == FALSE) {
|
|
free(origPath);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(record->recordType == kHFSPlusFileRecord) {
|
|
if((word + strlen(word) + 1) >= pathLimit) {
|
|
free(origPath);
|
|
|
|
if(retKey != NULL) {
|
|
memcpy(retKey, &key, sizeof(HFSPlusCatalogKey));
|
|
}
|
|
|
|
return record;
|
|
} else {
|
|
free(origPath);
|
|
free(record);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if(record->recordType != kHFSPlusFolderRecord)
|
|
hfs_panic("inconsistent catalog tree!");
|
|
|
|
realParent = key.parentID;
|
|
key.parentID = ((HFSPlusCatalogFolder*)record)->folderID;
|
|
}
|
|
|
|
if(retKey != NULL) {
|
|
memcpy(retKey, &key, sizeof(HFSPlusCatalogKey));
|
|
retKey->parentID = realParent;
|
|
}
|
|
|
|
free(origPath);
|
|
return record;
|
|
}
|
|
|
|
int updateCatalog(Volume* volume, HFSPlusCatalogRecord* catalogRecord) {
|
|
HFSPlusCatalogKey key;
|
|
HFSPlusCatalogRecord* record;
|
|
HFSPlusCatalogFile file;
|
|
HFSPlusCatalogFolder folder;
|
|
int exact;
|
|
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
if(catalogRecord->recordType == kHFSPlusFolderRecord) {
|
|
key.parentID = ((HFSPlusCatalogFolder*)catalogRecord)->folderID;
|
|
} else if(catalogRecord->recordType == kHFSPlusFileRecord) {
|
|
key.parentID = ((HFSPlusCatalogFile*)catalogRecord)->fileID;
|
|
} else {
|
|
/* unexpected */
|
|
return FALSE;
|
|
}
|
|
key.nodeName.length = 0;
|
|
|
|
record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
|
|
key.parentID = ((HFSPlusCatalogThread*)record)->parentID;
|
|
key.nodeName = ((HFSPlusCatalogThread*)record)->nodeName;
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length) + (sizeof(uint16_t) * key.nodeName.length);
|
|
|
|
free(record);
|
|
|
|
record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
|
|
|
|
removeFromBTree(volume->catalogTree, (BTKey*)(&key));
|
|
|
|
switch(record->recordType) {
|
|
case kHFSPlusFolderRecord:
|
|
memcpy(&folder, catalogRecord, sizeof(HFSPlusCatalogFolder));
|
|
flipCatalogFolder(&folder);
|
|
free(record);
|
|
return addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFolder), (unsigned char *)(&folder));
|
|
break;
|
|
|
|
case kHFSPlusFileRecord:
|
|
memcpy(&file, catalogRecord, sizeof(HFSPlusCatalogFile));
|
|
flipCatalogFile(&file);
|
|
free(record);
|
|
return addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFile), (unsigned char *)(&file));
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int move(const char* source, const char* dest, Volume* volume) {
|
|
HFSPlusCatalogRecord* srcRec;
|
|
HFSPlusCatalogFolder* srcFolderRec;
|
|
HFSPlusCatalogFolder* destRec;
|
|
char* destPath;
|
|
char* destName;
|
|
char* curChar;
|
|
char* lastSeparator;
|
|
|
|
int i;
|
|
int threadLength;
|
|
|
|
HFSPlusCatalogKey srcKey;
|
|
HFSPlusCatalogKey destKey;
|
|
HFSPlusCatalogThread* thread;
|
|
|
|
srcRec = getRecordFromPath3(source, volume, NULL, &srcKey, TRUE, FALSE, kHFSRootFolderID);
|
|
if(srcRec == NULL) {
|
|
free(srcRec);
|
|
return FALSE;
|
|
}
|
|
|
|
srcFolderRec = (HFSPlusCatalogFolder*) getRecordByCNID(srcKey.parentID, volume);
|
|
|
|
if(srcFolderRec == NULL || srcFolderRec->recordType != kHFSPlusFolderRecord) {
|
|
free(srcRec);
|
|
free(srcFolderRec);
|
|
return FALSE;
|
|
}
|
|
|
|
destPath = strdup(dest);
|
|
|
|
curChar = destPath;
|
|
lastSeparator = NULL;
|
|
|
|
while((*curChar) != '\0') {
|
|
if((*curChar) == '/')
|
|
lastSeparator = curChar;
|
|
curChar++;
|
|
}
|
|
|
|
if(lastSeparator == NULL) {
|
|
destRec = (HFSPlusCatalogFolder*) getRecordFromPath("/", volume, NULL, NULL);
|
|
destName = destPath;
|
|
} else {
|
|
destName = lastSeparator + 1;
|
|
*lastSeparator = '\0';
|
|
destRec = (HFSPlusCatalogFolder*) getRecordFromPath(destPath, volume, NULL, NULL);
|
|
|
|
if(destRec == NULL || destRec->recordType != kHFSPlusFolderRecord) {
|
|
free(destPath);
|
|
free(srcRec);
|
|
free(destRec);
|
|
free(srcFolderRec);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
removeFromBTree(volume->catalogTree, (BTKey*)(&srcKey));
|
|
|
|
srcKey.nodeName.length = 0;
|
|
if(srcRec->recordType == kHFSPlusFolderRecord) {
|
|
srcKey.parentID = ((HFSPlusCatalogFolder*)srcRec)->folderID;
|
|
} else if(srcRec->recordType == kHFSPlusFileRecord) {
|
|
srcKey.parentID = ((HFSPlusCatalogFile*)srcRec)->fileID;
|
|
} else {
|
|
/* unexpected */
|
|
return FALSE;
|
|
}
|
|
srcKey.keyLength = sizeof(srcKey.parentID) + sizeof(srcKey.nodeName.length);
|
|
|
|
removeFromBTree(volume->catalogTree, (BTKey*)(&srcKey));
|
|
|
|
|
|
destKey.nodeName.length = strlen(destName);
|
|
|
|
threadLength = sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint16_t) + (sizeof(uint16_t) * destKey.nodeName.length);
|
|
thread = (HFSPlusCatalogThread*) malloc(threadLength);
|
|
thread->reserved = 0;
|
|
destKey.parentID = destRec->folderID;
|
|
thread->parentID = destKey.parentID;
|
|
thread->nodeName.length = destKey.nodeName.length;
|
|
for(i = 0; i < destKey.nodeName.length; i++) {
|
|
destKey.nodeName.unicode[i] = destName[i];
|
|
thread->nodeName.unicode[i] = destName[i];
|
|
}
|
|
|
|
destKey.keyLength = sizeof(uint32_t) + sizeof(uint16_t) + (sizeof(uint16_t) * destKey.nodeName.length);
|
|
|
|
switch(srcRec->recordType) {
|
|
case kHFSPlusFolderRecord:
|
|
thread->recordType = kHFSPlusFolderThreadRecord;
|
|
flipCatalogFolder((HFSPlusCatalogFolder*)srcRec);
|
|
addToBTree(volume->catalogTree, (BTKey*)(&destKey), sizeof(HFSPlusCatalogFolder), (unsigned char *)(srcRec));
|
|
break;
|
|
|
|
case kHFSPlusFileRecord:
|
|
thread->recordType = kHFSPlusFileThreadRecord;
|
|
flipCatalogFile((HFSPlusCatalogFile*)srcRec);
|
|
addToBTree(volume->catalogTree, (BTKey*)(&destKey), sizeof(HFSPlusCatalogFile), (unsigned char *)(srcRec));
|
|
break;
|
|
}
|
|
|
|
destKey.nodeName.length = 0;
|
|
destKey.parentID = srcKey.parentID;
|
|
destKey.keyLength = sizeof(destKey.parentID) + sizeof(destKey.nodeName.length);
|
|
|
|
flipCatalogThread(thread, TRUE);
|
|
addToBTree(volume->catalogTree, (BTKey*)(&destKey), threadLength, (unsigned char *)(thread));
|
|
|
|
/* adjust valence */
|
|
srcFolderRec->valence--;
|
|
updateCatalog(volume, (HFSPlusCatalogRecord*) srcFolderRec);
|
|
destRec->valence++;
|
|
updateCatalog(volume, (HFSPlusCatalogRecord*) destRec);
|
|
|
|
free(thread);
|
|
free(destPath);
|
|
free(srcRec);
|
|
free(destRec);
|
|
free(srcFolderRec);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int removeFile(const char* fileName, Volume* volume) {
|
|
HFSPlusCatalogRecord* record;
|
|
HFSPlusCatalogKey key;
|
|
io_func* io;
|
|
HFSPlusCatalogFolder* parentFolder;
|
|
|
|
record = getRecordFromPath3(fileName, volume, NULL, &key, TRUE, FALSE, kHFSRootFolderID);
|
|
if(record != NULL) {
|
|
parentFolder = (HFSPlusCatalogFolder*) getRecordByCNID(key.parentID, volume);
|
|
if(parentFolder != NULL) {
|
|
if(parentFolder->recordType != kHFSPlusFolderRecord) {
|
|
ASSERT(FALSE, "parent not folder");
|
|
free(parentFolder);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
ASSERT(FALSE, "can't find parent");
|
|
return FALSE;
|
|
}
|
|
|
|
if(record->recordType == kHFSPlusFileRecord) {
|
|
io = openRawFile(((HFSPlusCatalogFile*)record)->fileID, &((HFSPlusCatalogFile*)record)->dataFork, record, volume);
|
|
allocate((RawFile*)io->data, 0);
|
|
CLOSE(io);
|
|
|
|
removeFromBTree(volume->catalogTree, (BTKey*)(&key));
|
|
XAttrList* next;
|
|
XAttrList* attrs = getAllExtendedAttributes(((HFSPlusCatalogFile*)record)->fileID, volume);
|
|
if(attrs != NULL) {
|
|
while(attrs != NULL) {
|
|
next = attrs->next;
|
|
unsetAttribute(volume, ((HFSPlusCatalogFile*)record)->fileID, attrs->name);
|
|
free(attrs->name);
|
|
free(attrs);
|
|
attrs = next;
|
|
}
|
|
}
|
|
|
|
|
|
key.nodeName.length = 0;
|
|
key.parentID = ((HFSPlusCatalogFile*)record)->fileID;
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
removeFromBTree(volume->catalogTree, (BTKey*)(&key));
|
|
|
|
volume->volumeHeader->fileCount--;
|
|
} else {
|
|
if(((HFSPlusCatalogFolder*)record)->valence > 0) {
|
|
free(record);
|
|
free(parentFolder);
|
|
ASSERT(FALSE, "folder not empty");
|
|
return FALSE;
|
|
} else {
|
|
removeFromBTree(volume->catalogTree, (BTKey*)(&key));
|
|
XAttrList* next;
|
|
XAttrList* attrs = getAllExtendedAttributes(((HFSPlusCatalogFolder*)record)->folderID, volume);
|
|
if(attrs != NULL) {
|
|
while(attrs != NULL) {
|
|
next = attrs->next;
|
|
unsetAttribute(volume, ((HFSPlusCatalogFolder*)record)->folderID, attrs->name);
|
|
free(attrs->name);
|
|
free(attrs);
|
|
attrs = next;
|
|
}
|
|
}
|
|
|
|
key.nodeName.length = 0;
|
|
key.parentID = ((HFSPlusCatalogFolder*)record)->folderID;
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
removeFromBTree(volume->catalogTree, (BTKey*)(&key));
|
|
}
|
|
|
|
parentFolder->folderCount--;
|
|
volume->volumeHeader->folderCount--;
|
|
}
|
|
parentFolder->valence--;
|
|
updateCatalog(volume, (HFSPlusCatalogRecord*) parentFolder);
|
|
updateVolume(volume);
|
|
|
|
free(record);
|
|
free(parentFolder);
|
|
|
|
return TRUE;
|
|
} else {
|
|
free(parentFolder);
|
|
ASSERT(FALSE, "cannot find record");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
int makeSymlink(const char* pathName, const char* target, Volume* volume) {
|
|
io_func* io;
|
|
HFSPlusCatalogFile* record;
|
|
|
|
record = (HFSPlusCatalogFile*) getRecordFromPath3(pathName, volume, NULL, NULL, TRUE, FALSE, kHFSRootFolderID);
|
|
|
|
if(!record) {
|
|
newFile(pathName, volume);
|
|
record = (HFSPlusCatalogFile*) getRecordFromPath(pathName, volume, NULL, NULL);
|
|
if(!record) {
|
|
return FALSE;
|
|
}
|
|
record->permissions.fileMode |= S_IFLNK;
|
|
record->userInfo.fileType = kSymLinkFileType;
|
|
record->userInfo.fileCreator = kSymLinkCreator;
|
|
updateCatalog(volume, (HFSPlusCatalogRecord*) record);
|
|
} else {
|
|
if(record->recordType != kHFSPlusFileRecord || (((HFSPlusCatalogFile*)record)->permissions.fileMode & S_IFLNK) != S_IFLNK) {
|
|
free(record);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
io = openRawFile(record->fileID, &record->dataFork, (HFSPlusCatalogRecord*) record, volume);
|
|
WRITE(io, 0, strlen(target), (void*) target);
|
|
CLOSE(io);
|
|
free(record);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HFSCatalogNodeID newFolder(const char* pathName, Volume* volume) {
|
|
HFSPlusCatalogFolder* parentFolder;
|
|
HFSPlusCatalogFolder folder;
|
|
HFSPlusCatalogKey key;
|
|
HFSPlusCatalogThread thread;
|
|
|
|
uint32_t newFolderID;
|
|
|
|
int threadLength;
|
|
|
|
char* path;
|
|
char* name;
|
|
char* curChar;
|
|
char* lastSeparator;
|
|
|
|
path = strdup(pathName);
|
|
|
|
curChar = path;
|
|
lastSeparator = NULL;
|
|
|
|
while((*curChar) != '\0') {
|
|
if((*curChar) == '/')
|
|
lastSeparator = curChar;
|
|
curChar++;
|
|
}
|
|
|
|
if(lastSeparator == NULL) {
|
|
parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath("/", volume, NULL, NULL);
|
|
name = path;
|
|
} else {
|
|
name = lastSeparator + 1;
|
|
*lastSeparator = '\0';
|
|
parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath(path, volume, NULL, NULL);
|
|
}
|
|
|
|
if(parentFolder == NULL || parentFolder->recordType != kHFSPlusFolderRecord) {
|
|
free(path);
|
|
free(parentFolder);
|
|
return FALSE;
|
|
}
|
|
|
|
newFolderID = volume->volumeHeader->nextCatalogID++;
|
|
volume->volumeHeader->folderCount++;
|
|
|
|
folder.recordType = kHFSPlusFolderRecord;
|
|
folder.flags = kHFSHasFolderCountMask;
|
|
folder.valence = 0;
|
|
folder.folderID = newFolderID;
|
|
folder.createDate = UNIX_TO_APPLE_TIME(time(NULL));
|
|
folder.contentModDate = folder.createDate;
|
|
folder.attributeModDate = folder.createDate;
|
|
folder.accessDate = folder.createDate;
|
|
folder.backupDate = folder.createDate;
|
|
folder.permissions.ownerID = parentFolder->permissions.ownerID;
|
|
folder.permissions.groupID = parentFolder->permissions.groupID;
|
|
folder.permissions.adminFlags = 0;
|
|
folder.permissions.ownerFlags = 0;
|
|
folder.permissions.fileMode = S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
|
|
folder.permissions.special.iNodeNum = 0;
|
|
memset(&folder.userInfo, 0, sizeof(folder.userInfo));
|
|
memset(&folder.finderInfo, 0, sizeof(folder.finderInfo));
|
|
folder.textEncoding = 0;
|
|
folder.folderCount = 0;
|
|
|
|
key.parentID = parentFolder->folderID;
|
|
ASCIIToUnicode(name, &key.nodeName);
|
|
key.keyLength = sizeof(key.parentID) + STR_SIZE(key.nodeName);
|
|
|
|
thread.recordType = kHFSPlusFolderThreadRecord;
|
|
thread.reserved = 0;
|
|
thread.parentID = parentFolder->folderID;
|
|
ASCIIToUnicode(name, &thread.nodeName);
|
|
threadLength = sizeof(thread.recordType) + sizeof(thread.reserved) + sizeof(thread.parentID) + STR_SIZE(thread.nodeName);
|
|
flipCatalogThread(&thread, TRUE);
|
|
flipCatalogFolder(&folder);
|
|
|
|
ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFolder), (unsigned char *)(&folder)), "addToBTree");
|
|
key.nodeName.length = 0;
|
|
key.parentID = newFolderID;
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), threadLength, (unsigned char *)(&thread)), "addToBTree");
|
|
|
|
parentFolder->folderCount++;
|
|
parentFolder->valence++;
|
|
updateCatalog(volume, (HFSPlusCatalogRecord*) parentFolder);
|
|
|
|
updateVolume(volume);
|
|
|
|
free(parentFolder);
|
|
free(path);
|
|
|
|
return newFolderID;
|
|
}
|
|
|
|
HFSCatalogNodeID newFile(const char* pathName, Volume* volume) {
|
|
HFSPlusCatalogFolder* parentFolder;
|
|
HFSPlusCatalogFile file;
|
|
HFSPlusCatalogKey key;
|
|
HFSPlusCatalogThread thread;
|
|
|
|
uint32_t newFileID;
|
|
|
|
int threadLength;
|
|
|
|
char* path;
|
|
char* name;
|
|
char* curChar;
|
|
char* lastSeparator;
|
|
|
|
path = strdup(pathName);
|
|
|
|
curChar = path;
|
|
lastSeparator = NULL;
|
|
|
|
while((*curChar) != '\0') {
|
|
if((*curChar) == '/')
|
|
lastSeparator = curChar;
|
|
curChar++;
|
|
}
|
|
|
|
if(lastSeparator == NULL) {
|
|
parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath("/", volume, NULL, NULL);
|
|
name = path;
|
|
} else {
|
|
name = lastSeparator + 1;
|
|
*lastSeparator = '\0';
|
|
parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath(path, volume, NULL, NULL);
|
|
}
|
|
|
|
if(parentFolder == NULL || parentFolder->recordType != kHFSPlusFolderRecord) {
|
|
free(path);
|
|
free(parentFolder);
|
|
return FALSE;
|
|
}
|
|
|
|
newFileID = volume->volumeHeader->nextCatalogID++;
|
|
volume->volumeHeader->fileCount++;
|
|
|
|
file.recordType = kHFSPlusFileRecord;
|
|
file.flags = kHFSThreadExistsMask;
|
|
file.reserved1 = 0;
|
|
file.fileID = newFileID;
|
|
file.createDate = UNIX_TO_APPLE_TIME(time(NULL));
|
|
file.contentModDate = file.createDate;
|
|
file.attributeModDate = file.createDate;
|
|
file.accessDate = file.createDate;
|
|
file.backupDate = file.createDate;
|
|
file.permissions.ownerID = parentFolder->permissions.ownerID;
|
|
file.permissions.groupID = parentFolder->permissions.groupID;
|
|
file.permissions.adminFlags = 0;
|
|
file.permissions.ownerFlags = 0;
|
|
file.permissions.fileMode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
|
file.permissions.special.iNodeNum = 0;
|
|
memset(&file.userInfo, 0, sizeof(file.userInfo));
|
|
memset(&file.finderInfo, 0, sizeof(file.finderInfo));
|
|
file.textEncoding = 0;
|
|
file.reserved2 = 0;
|
|
memset(&file.dataFork, 0, sizeof(file.dataFork));
|
|
memset(&file.resourceFork, 0, sizeof(file.resourceFork));
|
|
|
|
key.parentID = parentFolder->folderID;
|
|
ASCIIToUnicode(name, &key.nodeName);
|
|
key.keyLength = sizeof(key.parentID) + STR_SIZE(key.nodeName);
|
|
|
|
thread.recordType = kHFSPlusFileThreadRecord;
|
|
thread.reserved = 0;
|
|
thread.parentID = parentFolder->folderID;
|
|
ASCIIToUnicode(name, &thread.nodeName);
|
|
threadLength = sizeof(thread.recordType) + sizeof(thread.reserved) + sizeof(thread.parentID) + STR_SIZE(thread.nodeName);
|
|
flipCatalogThread(&thread, TRUE);
|
|
flipCatalogFile(&file);
|
|
|
|
ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFile), (unsigned char *)(&file)), "addToBTree");
|
|
key.nodeName.length = 0;
|
|
key.parentID = newFileID;
|
|
key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
|
|
ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), threadLength, (unsigned char *)(&thread)), "addToBTree");
|
|
|
|
parentFolder->valence++;
|
|
updateCatalog(volume, (HFSPlusCatalogRecord*) parentFolder);
|
|
|
|
updateVolume(volume);
|
|
|
|
free(parentFolder);
|
|
free(path);
|
|
|
|
return newFileID;
|
|
}
|
|
|
|
int chmodFile(const char* pathName, int mode, Volume* volume) {
|
|
HFSPlusCatalogRecord* record;
|
|
|
|
record = getRecordFromPath(pathName, volume, NULL, NULL);
|
|
|
|
if(record == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(record->recordType == kHFSPlusFolderRecord) {
|
|
((HFSPlusCatalogFolder*)record)->permissions.fileMode = (((HFSPlusCatalogFolder*)record)->permissions.fileMode & 0770000) | mode;
|
|
} else if(record->recordType == kHFSPlusFileRecord) {
|
|
((HFSPlusCatalogFile*)record)->permissions.fileMode = (((HFSPlusCatalogFolder*)record)->permissions.fileMode & 0770000) | mode;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
updateCatalog(volume, record);
|
|
|
|
free(record);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int chownFile(const char* pathName, uint32_t owner, uint32_t group, Volume* volume) {
|
|
HFSPlusCatalogRecord* record;
|
|
|
|
record = getRecordFromPath(pathName, volume, NULL, NULL);
|
|
|
|
if(record == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(record->recordType == kHFSPlusFolderRecord) {
|
|
((HFSPlusCatalogFolder*)record)->permissions.ownerID = owner;
|
|
((HFSPlusCatalogFolder*)record)->permissions.groupID = group;
|
|
} else if(record->recordType == kHFSPlusFileRecord) {
|
|
((HFSPlusCatalogFile*)record)->permissions.ownerID = owner;
|
|
((HFSPlusCatalogFile*)record)->permissions.groupID = group;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
updateCatalog(volume, record);
|
|
|
|
free(record);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BTree* openCatalogTree(io_func* file) {
|
|
BTree* btree;
|
|
|
|
btree = openBTree(file, &catalogCompare, &catalogKeyRead, &catalogKeyWrite, &catalogKeyPrint, &catalogDataRead);
|
|
|
|
if(btree->headerRec->keyCompareType == kHFSCaseFolding) {
|
|
btree->compare = &catalogCompareCS;
|
|
}
|
|
|
|
return btree;
|
|
}
|
|
|