Add HFSDiskImage class for creating disk images and adding files to them

This commit is contained in:
Lieven Dekeyser 2024-03-15 19:30:43 +01:00
parent 7060fc5b23
commit aa64a915fa
3 changed files with 376 additions and 0 deletions

View File

@ -153,6 +153,7 @@
28F6B4C21CF07F5C002D76D0 /* liblibres.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F6B4B61CF07F32002D76D0 /* liblibres.a */; };
28F6B4CA1CF1FA7A002D76D0 /* about.plist in Resources */ = {isa = PBXBuildFile; fileRef = 28F6B4C91CF1FA7A002D76D0 /* about.plist */; };
28F6B4CF1CF77099002D76D0 /* compat.m in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4CE1CF77099002D76D0 /* compat.m */; };
CA06D3352BA25DC10019B7B7 /* HFSDiskImage.m in Sources */ = {isa = PBXBuildFile; fileRef = CA06D3332BA25DC00019B7B7 /* HFSDiskImage.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -371,6 +372,8 @@
28F6B4CE1CF77099002D76D0 /* compat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = compat.m; sourceTree = "<group>"; };
28F875921D29402B001E99EB /* PlugIn-Capabilities.plist.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "PlugIn-Capabilities.plist.xml"; sourceTree = "<group>"; };
CA06D3322BA255830019B7B7 /* CodeSigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CodeSigning.xcconfig; sourceTree = "<group>"; };
CA06D3332BA25DC00019B7B7 /* HFSDiskImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HFSDiskImage.m; sourceTree = "<group>"; };
CA06D3342BA25DC00019B7B7 /* HFSDiskImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HFSDiskImage.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -617,6 +620,8 @@
28848B611CDE97D600B86C45 /* InsertDiskViewController.m */,
28848B631CDE97E900B86C45 /* SettingsViewController.h */,
28848B641CDE97E900B86C45 /* SettingsViewController.m */,
CA06D3342BA25DC00019B7B7 /* HFSDiskImage.h */,
CA06D3332BA25DC00019B7B7 /* HFSDiskImage.m */,
28F676C91CD15E0B00FC6FA6 /* Main.storyboard */,
28F676CC1CD15E0B00FC6FA6 /* Assets.xcassets */,
28BA896E1CE7314500A98104 /* Keyboard */,
@ -1301,6 +1306,7 @@
28D5A3FD1CD6868F001A33F6 /* TouchScreen.m in Sources */,
28F676C51CD15E0B00FC6FA6 /* AppDelegate.m in Sources */,
28BA89801CE7315400A98104 /* KBKeyboardView.m in Sources */,
CA06D3352BA25DC10019B7B7 /* HFSDiskImage.m in Sources */,
28F676C21CD15E0B00FC6FA6 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

42
Mini vMac/HFSDiskImage.h Normal file
View File

@ -0,0 +1,42 @@
//
// HFSDiskImage.h
// Mini vMac
//
// Created by Lieven Dekeyser on 13/03/2024.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HFSDiskImage : NSObject
@property (nonatomic, readonly, copy) NSString * path;
@property (nonatomic, readonly, getter=isOpen) BOOL open;
@property (nonatomic, readonly, getter=isReadOnly) BOOL readOnly;
+ (nullable HFSDiskImage *)createDiskImageWithName:(NSString *)name size:(size_t)volumeSize atPath:(NSString *)path;
- (nullable instancetype)initWithPath:(NSString *)path;
- (instancetype)init __unavailable;
+ (instancetype)new __unavailable;
- (BOOL)openForReading;
- (BOOL)openForReadingAndWriting;
- (BOOL)close;
- (BOOL)addFile:(NSString *)sourceFile;
@end // HFSDiskImage
@interface HFSDiskImage (Import)
+ (nullable HFSDiskImage *)importFileIntoTemporaryDiskImage:(NSString *)sourceFile;
@end // HFSDiskImage (Import)
NS_ASSUME_NONNULL_END

328
Mini vMac/HFSDiskImage.m Normal file
View File

@ -0,0 +1,328 @@
//
// HFSDiskImage.m
// Mini vMac
//
// Created by Lieven Dekeyser on 13/03/2024.
//
#import "HFSDiskImage.h"
#include "libhfs.h"
@interface NSString (HFSSafe)
- (nullable NSString *)hfsSafeFileName;
- (nullable NSString *)hfsSafeVolumeName;
@end // NSString (HFSSafe)
@interface HFSDiskImage ()
@property (nonatomic, copy) NSString * path;
@end // HFSDiskImage()
@implementation HFSDiskImage
{
hfsvol * _volume;
}
+ (nullable HFSDiskImage *)createDiskImageWithName:(NSString *)name size:(size_t)volumeSize atPath:(NSString *)path
{
NSFileManager * fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:path])
{
return nil;
}
int fileDescriptor = open(path.fileSystemRepresentation, O_CREAT | O_TRUNC | O_EXCL | O_WRONLY, 0644);
if (fileDescriptor == -1)
{
return nil;
}
int error = 0;
if (ftruncate(fileDescriptor, volumeSize))
{
error = errno;
}
else
{
const char * volumeName = [name cStringUsingEncoding:NSMacOSRomanStringEncoding];
if (volumeName != nil)
{
error = hfs_format(path.fileSystemRepresentation, 0, 0, volumeName, 0, NULL);
}
else
{
error = EINVAL;
}
}
close(fileDescriptor);
if (error != 0)
{
[fm removeItemAtPath:path error:nil];
return nil;
}
return [[self alloc] initWithPath:path];
}
- (nullable instancetype)initWithPath:(NSString *)path
{
if ((self = [super init]))
{
_readOnly = NO;
_volume = nil;
self.path = path;
}
return self;
}
- (void)dealloc
{
[self close];
}
- (BOOL)isOpen
{
return _volume != NULL;
}
- (BOOL)openForReading
{
return [self _openWithMode:HFS_MODE_RDONLY];
}
- (BOOL)openForReadingAndWriting
{
return [self _openWithMode:HFS_MODE_RDWR];
}
- (BOOL)_openWithMode:(int)mode
{
if ([self isOpen])
{
return YES;
}
_volume = hfs_mount(self.path.fileSystemRepresentation, 0, mode);
_readOnly = (mode != HFS_MODE_RDWR);
return [self isOpen];
}
- (BOOL)close
{
if (_volume)
{
int error = hfs_umount(_volume);
if (error == 0) {
_volume = NULL;
}
}
return ![self isOpen];
}
- (BOOL)addFile:(NSString *)sourceFilePath
{
if (![self isOpen] || [self isReadOnly])
{
return NO;
}
NSString * fileName = [[sourceFilePath lastPathComponent] hfsSafeFileName];
if (fileName == nil)
{
return NO;
}
const char * targetPath = [[NSString stringWithFormat:@":%@", fileName] cStringUsingEncoding:NSMacOSRomanStringEncoding];
if (targetPath == NULL)
{
return NO;
}
FILE * sourceFile = fopen(sourceFilePath.fileSystemRepresentation, "r");
if (sourceFile == NULL)
{
return NO;
}
BOOL error = NO;
hfsfile * file = hfs_create(_volume, targetPath, "SIT!", "SITx"); // FIXME: type and creator from extension
if (file)
{
const size_t bufferSize = HFS_BLOCKSZ;
uint8_t buffer[bufferSize] = { 0 };
size_t bytesRead = 0;
while ((bytesRead = fread(buffer, 1, bufferSize, sourceFile)) > 0)
{
unsigned long bytesWritten = hfs_write(file, buffer, bytesRead);
if (bytesWritten < bytesRead)
{
error = YES;
break;
}
}
hfs_close(file);
file = NULL;
}
else
{
error = YES;
}
fclose(sourceFile);
sourceFile = NULL;
return error == NO;
}
@end // HFSDiskImage
@implementation HFSDiskImage (Import)
+ (HFSDiskImage *)importFileIntoTemporaryDiskImage:(NSString *)sourceFile
{
NSFileManager * fm = [NSFileManager defaultManager];
NSError * error = nil;
size_t fileSize = [fm attributesOfItemAtPath:sourceFile error:&error].fileSize;
if (fileSize == 0)
{
return nil;
}
NSString * tempFolder = NSTemporaryDirectory();
NSString * volumeName = [[[sourceFile lastPathComponent] stringByDeletingPathExtension] hfsSafeVolumeName];
if (volumeName == nil)
{
return nil;
}
NSString * diskImagePath = [tempFolder stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.img", volumeName]];
int tries = 1;
while ([fm fileExistsAtPath:diskImagePath])
{
++tries;
diskImagePath = [tempFolder stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ %d.img", volumeName, tries]];
}
HFSDiskImage * diskImage = [HFSDiskImage createDiskImageWithName:volumeName size:(fileSize + (512*1024)) atPath:diskImagePath];
if (diskImage == NULL || ![diskImage openForReadingAndWriting])
{
return nil;
}
BOOL result = [diskImage addFile:sourceFile];
[diskImage close];
if (result)
{
return diskImage;
}
else
{
return nil;
}
}
@end // HFSDiskImage (Import)
@implementation NSString (HFSSafe)
- (NSString *)macOSRomanSafeStringGettingLength:(NSUInteger *)outLength
{
NSData * converted = [self dataUsingEncoding:NSMacOSRomanStringEncoding allowLossyConversion:YES];
if (converted)
{
if (outLength)
{
*outLength = [converted length];
}
return [[NSString alloc] initWithData:converted encoding:NSMacOSRomanStringEncoding];
}
else
{
return nil;
}
}
- (NSString *)macOSRomanSafeStringWithMaxLength:(NSUInteger)maxLength
{
NSData * converted = [self dataUsingEncoding:NSMacOSRomanStringEncoding allowLossyConversion:YES];
if (converted)
{
NSUInteger convertedLength = [converted length];
if (convertedLength > maxLength)
{
converted = [converted subdataWithRange:NSMakeRange(0, maxLength)];
}
return [[NSString alloc] initWithData:converted encoding:NSMacOSRomanStringEncoding];
}
else
{
return nil;
}
}
- (NSString *)hfsSafeFileName
{
NSString * noColons = [self stringByReplacingOccurrencesOfString:@":" withString:@"_"];
NSUInteger convertedStringLength = 0;
NSString * convertedString = [noColons macOSRomanSafeStringGettingLength:&convertedStringLength];
if (convertedString)
{
if (convertedStringLength <= HFS_MAX_FLEN)
{
return convertedString;
}
else
{
// keep path extension if possible:
NSUInteger pathExtensionLength = 0;
NSString * pathExtension = [[self pathExtension] macOSRomanSafeStringGettingLength:&pathExtensionLength];
if (pathExtension)
{
NSInteger remainingLength = HFS_MAX_FLEN - pathExtensionLength - 1;
if (remainingLength > 2)
{
NSString * trimmedName = [[self stringByDeletingPathExtension] macOSRomanSafeStringWithMaxLength:remainingLength];
return [trimmedName stringByAppendingPathExtension:pathExtension];
}
else
{
return [[self stringByDeletingPathExtension] macOSRomanSafeStringWithMaxLength:HFS_MAX_FLEN];
}
}
else
{
return [self macOSRomanSafeStringWithMaxLength:HFS_MAX_FLEN];
}
}
}
else
{
return nil;
}
}
- (NSString *)hfsSafeVolumeName
{
NSString * noColons = [self stringByReplacingOccurrencesOfString:@":" withString:@"_"];
return [noColons macOSRomanSafeStringWithMaxLength:HFS_MAX_VLEN];
}
@end // NSString (HFSSafe)