diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6742bfc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libmfs"] + path = libmfs + url = https://github.com/zydeco/libmfs.git +[submodule "libres"] + path = libres + url = https://github.com/zydeco/libres.git diff --git a/Mini vMac.xcodeproj/project.pbxproj b/Mini vMac.xcodeproj/project.pbxproj index 401589f..af506c0 100644 --- a/Mini vMac.xcodeproj/project.pbxproj +++ b/Mini vMac.xcodeproj/project.pbxproj @@ -39,8 +39,58 @@ 28F676CB1CD15E0B00FC6FA6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 28F676C91CD15E0B00FC6FA6 /* Main.storyboard */; }; 28F676CD1CD15E0B00FC6FA6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 28F676CC1CD15E0B00FC6FA6 /* Assets.xcassets */; }; 28F676D01CD15E0B00FC6FA6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 28F676CE1CD15E0B00FC6FA6 /* LaunchScreen.storyboard */; }; + 28F6B4521CF07C48002D76D0 /* UIImage+DiskImageIcon.m in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4511CF07C48002D76D0 /* UIImage+DiskImageIcon.m */; }; + 28F6B4971CF07E00002D76D0 /* block.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4611CF07CC9002D76D0 /* block.c */; }; + 28F6B4981CF07E00002D76D0 /* btree.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4631CF07CC9002D76D0 /* btree.c */; }; + 28F6B4991CF07E00002D76D0 /* data.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4661CF07CC9002D76D0 /* data.c */; }; + 28F6B49A1CF07E00002D76D0 /* file.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4681CF07CC9002D76D0 /* file.c */; }; + 28F6B49B1CF07E00002D76D0 /* hfs.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B46A1CF07CC9002D76D0 /* hfs.c */; }; + 28F6B49C1CF07E00002D76D0 /* low.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B46D1CF07CC9002D76D0 /* low.c */; }; + 28F6B49D1CF07E00002D76D0 /* medium.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B46F1CF07CC9002D76D0 /* medium.c */; }; + 28F6B49E1CF07E00002D76D0 /* memcmp.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4711CF07CC9002D76D0 /* memcmp.c */; }; + 28F6B49F1CF07E00002D76D0 /* node.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4721CF07CC9002D76D0 /* node.c */; }; + 28F6B4A01CF07E00002D76D0 /* record.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4771CF07CC9002D76D0 /* record.c */; }; + 28F6B4A11CF07E00002D76D0 /* version.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4791CF07CC9002D76D0 /* version.c */; }; + 28F6B4A21CF07E00002D76D0 /* volume.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B47B1CF07CC9002D76D0 /* volume.c */; }; + 28F6B4A31CF07E08002D76D0 /* unix.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4751CF07CC9002D76D0 /* unix.c */; }; + 28F6B4B11CF07ED9002D76D0 /* mfs.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B45C1CF07CBF002D76D0 /* mfs.c */; }; + 28F6B4BF1CF07F39002D76D0 /* res.c in Sources */ = {isa = PBXBuildFile; fileRef = 28F6B4571CF07CB3002D76D0 /* res.c */; }; + 28F6B4C01CF07F5C002D76D0 /* liblibhfs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F6B48E1CF07DDD002D76D0 /* liblibhfs.a */; }; + 28F6B4C11CF07F5C002D76D0 /* liblibmfs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F6B4A81CF07EC9002D76D0 /* liblibmfs.a */; }; + 28F6B4C21CF07F5C002D76D0 /* liblibres.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F6B4B61CF07F32002D76D0 /* liblibres.a */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 28F6B48C1CF07DDD002D76D0 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 28F6B4A61CF07EC9002D76D0 /* Copy Files */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + name = "Copy Files"; + runOnlyForDeploymentPostprocessing = 0; + }; + 28F6B4B41CF07F32002D76D0 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 28848B601CDE97D600B86C45 /* InsertDiskViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InsertDiskViewController.h; sourceTree = ""; }; 28848B611CDE97D600B86C45 /* InsertDiskViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InsertDiskViewController.m; sourceTree = ""; }; @@ -113,10 +163,75 @@ 28F676CC1CD15E0B00FC6FA6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28F676CF1CD15E0B00FC6FA6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28F676D11CD15E0B00FC6FA6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 28F6B4501CF07C48002D76D0 /* UIImage+DiskImageIcon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+DiskImageIcon.h"; sourceTree = ""; }; + 28F6B4511CF07C48002D76D0 /* UIImage+DiskImageIcon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+DiskImageIcon.m"; sourceTree = ""; }; + 28F6B4561CF07CB3002D76D0 /* libres_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libres_internal.h; sourceTree = ""; }; + 28F6B4571CF07CB3002D76D0 /* res.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = res.c; sourceTree = ""; }; + 28F6B4581CF07CB3002D76D0 /* res.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = res.h; sourceTree = ""; }; + 28F6B45A1CF07CBF002D76D0 /* appledouble.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = appledouble.h; sourceTree = ""; }; + 28F6B45B1CF07CBF002D76D0 /* fobj.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fobj.h; sourceTree = ""; }; + 28F6B45C1CF07CBF002D76D0 /* mfs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mfs.c; sourceTree = ""; }; + 28F6B45D1CF07CBF002D76D0 /* mfs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mfs.h; sourceTree = ""; }; + 28F6B45F1CF07CC9002D76D0 /* acconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = acconfig.h; sourceTree = ""; }; + 28F6B4601CF07CC9002D76D0 /* apple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = apple.h; sourceTree = ""; }; + 28F6B4611CF07CC9002D76D0 /* block.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = block.c; sourceTree = ""; }; + 28F6B4621CF07CC9002D76D0 /* block.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = block.h; sourceTree = ""; }; + 28F6B4631CF07CC9002D76D0 /* btree.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = btree.c; sourceTree = ""; }; + 28F6B4641CF07CC9002D76D0 /* btree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = btree.h; sourceTree = ""; }; + 28F6B4651CF07CC9002D76D0 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; + 28F6B4661CF07CC9002D76D0 /* data.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = data.c; sourceTree = ""; }; + 28F6B4671CF07CC9002D76D0 /* data.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = data.h; sourceTree = ""; }; + 28F6B4681CF07CC9002D76D0 /* file.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = file.c; sourceTree = ""; }; + 28F6B4691CF07CC9002D76D0 /* file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file.h; sourceTree = ""; }; + 28F6B46A1CF07CC9002D76D0 /* hfs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hfs.c; sourceTree = ""; }; + 28F6B46B1CF07CC9002D76D0 /* hfs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hfs.h; sourceTree = ""; }; + 28F6B46C1CF07CC9002D76D0 /* libhfs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libhfs.h; sourceTree = ""; }; + 28F6B46D1CF07CC9002D76D0 /* low.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = low.c; sourceTree = ""; }; + 28F6B46E1CF07CC9002D76D0 /* low.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = low.h; sourceTree = ""; }; + 28F6B46F1CF07CC9002D76D0 /* medium.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = medium.c; sourceTree = ""; }; + 28F6B4701CF07CC9002D76D0 /* medium.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = medium.h; sourceTree = ""; }; + 28F6B4711CF07CC9002D76D0 /* memcmp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = memcmp.c; sourceTree = ""; }; + 28F6B4721CF07CC9002D76D0 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = node.c; sourceTree = ""; }; + 28F6B4731CF07CC9002D76D0 /* node.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = node.h; sourceTree = ""; }; + 28F6B4751CF07CC9002D76D0 /* unix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unix.c; sourceTree = ""; }; + 28F6B4761CF07CC9002D76D0 /* os.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = os.h; sourceTree = ""; }; + 28F6B4771CF07CC9002D76D0 /* record.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = record.c; sourceTree = ""; }; + 28F6B4781CF07CC9002D76D0 /* record.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = record.h; sourceTree = ""; }; + 28F6B4791CF07CC9002D76D0 /* version.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = version.c; sourceTree = ""; }; + 28F6B47A1CF07CC9002D76D0 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + 28F6B47B1CF07CC9002D76D0 /* volume.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = volume.c; sourceTree = ""; }; + 28F6B47C1CF07CC9002D76D0 /* volume.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = volume.h; sourceTree = ""; }; + 28F6B48E1CF07DDD002D76D0 /* liblibhfs.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = liblibhfs.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 28F6B4A81CF07EC9002D76D0 /* liblibmfs.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = liblibmfs.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 28F6B4B61CF07F32002D76D0 /* liblibres.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = liblibres.a; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 28F676BA1CD15E0B00FC6FA6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 28F6B4C01CF07F5C002D76D0 /* liblibhfs.a in Frameworks */, + 28F6B4C11CF07F5C002D76D0 /* liblibmfs.a in Frameworks */, + 28F6B4C21CF07F5C002D76D0 /* liblibres.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 28F6B48B1CF07DDD002D76D0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 28F6B4A51CF07EC9002D76D0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 28F6B4B31CF07F32002D76D0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -207,6 +322,9 @@ isa = PBXGroup; children = ( 28F676BD1CD15E0B00FC6FA6 /* Mini vMac.app */, + 28F6B48E1CF07DDD002D76D0 /* liblibhfs.a */, + 28F6B4A81CF07EC9002D76D0 /* liblibmfs.a */, + 28F6B4B61CF07F32002D76D0 /* liblibres.a */, ); name = Products; sourceTree = ""; @@ -239,6 +357,8 @@ 28CE8E8E1CD4C3B200FE25A8 /* mnvm_core */, 28CE8E871CD4C33E00FE25A8 /* mnvm_cfg */, 28F676C01CD15E0B00FC6FA6 /* Supporting Files */, + 28F6B4501CF07C48002D76D0 /* UIImage+DiskImageIcon.h */, + 28F6B4511CF07C48002D76D0 /* UIImage+DiskImageIcon.m */, ); path = "Mini vMac"; sourceTree = ""; @@ -246,11 +366,82 @@ 28F676C01CD15E0B00FC6FA6 /* Supporting Files */ = { isa = PBXGroup; children = ( + 28F6B4551CF07C9A002D76D0 /* libhfs */, + 28F6B4541CF07C8D002D76D0 /* libmfs */, + 28F6B4531CF07C83002D76D0 /* libres */, 28F676C11CD15E0B00FC6FA6 /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; + 28F6B4531CF07C83002D76D0 /* libres */ = { + isa = PBXGroup; + children = ( + 28F6B4561CF07CB3002D76D0 /* libres_internal.h */, + 28F6B4571CF07CB3002D76D0 /* res.c */, + 28F6B4581CF07CB3002D76D0 /* res.h */, + ); + name = libres; + path = ../libres; + sourceTree = ""; + }; + 28F6B4541CF07C8D002D76D0 /* libmfs */ = { + isa = PBXGroup; + children = ( + 28F6B45A1CF07CBF002D76D0 /* appledouble.h */, + 28F6B45B1CF07CBF002D76D0 /* fobj.h */, + 28F6B45C1CF07CBF002D76D0 /* mfs.c */, + 28F6B45D1CF07CBF002D76D0 /* mfs.h */, + ); + name = libmfs; + path = ../libmfs; + sourceTree = ""; + }; + 28F6B4551CF07C9A002D76D0 /* libhfs */ = { + isa = PBXGroup; + children = ( + 28F6B45F1CF07CC9002D76D0 /* acconfig.h */, + 28F6B4601CF07CC9002D76D0 /* apple.h */, + 28F6B4611CF07CC9002D76D0 /* block.c */, + 28F6B4621CF07CC9002D76D0 /* block.h */, + 28F6B4631CF07CC9002D76D0 /* btree.c */, + 28F6B4641CF07CC9002D76D0 /* btree.h */, + 28F6B4651CF07CC9002D76D0 /* config.h */, + 28F6B4661CF07CC9002D76D0 /* data.c */, + 28F6B4671CF07CC9002D76D0 /* data.h */, + 28F6B4681CF07CC9002D76D0 /* file.c */, + 28F6B4691CF07CC9002D76D0 /* file.h */, + 28F6B46A1CF07CC9002D76D0 /* hfs.c */, + 28F6B46B1CF07CC9002D76D0 /* hfs.h */, + 28F6B46C1CF07CC9002D76D0 /* libhfs.h */, + 28F6B46D1CF07CC9002D76D0 /* low.c */, + 28F6B46E1CF07CC9002D76D0 /* low.h */, + 28F6B46F1CF07CC9002D76D0 /* medium.c */, + 28F6B4701CF07CC9002D76D0 /* medium.h */, + 28F6B4711CF07CC9002D76D0 /* memcmp.c */, + 28F6B4721CF07CC9002D76D0 /* node.c */, + 28F6B4731CF07CC9002D76D0 /* node.h */, + 28F6B4741CF07CC9002D76D0 /* os */, + 28F6B4761CF07CC9002D76D0 /* os.h */, + 28F6B4771CF07CC9002D76D0 /* record.c */, + 28F6B4781CF07CC9002D76D0 /* record.h */, + 28F6B4791CF07CC9002D76D0 /* version.c */, + 28F6B47A1CF07CC9002D76D0 /* version.h */, + 28F6B47B1CF07CC9002D76D0 /* volume.c */, + 28F6B47C1CF07CC9002D76D0 /* volume.h */, + ); + name = libhfs; + path = ../libhfs; + sourceTree = ""; + }; + 28F6B4741CF07CC9002D76D0 /* os */ = { + isa = PBXGroup; + children = ( + 28F6B4751CF07CC9002D76D0 /* unix.c */, + ); + path = os; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -271,6 +462,57 @@ productReference = 28F676BD1CD15E0B00FC6FA6 /* Mini vMac.app */; productType = "com.apple.product-type.application"; }; + 28F6B48D1CF07DDD002D76D0 /* libhfs */ = { + isa = PBXNativeTarget; + buildConfigurationList = 28F6B4941CF07DDD002D76D0 /* Build configuration list for PBXNativeTarget "libhfs" */; + buildPhases = ( + 28F6B48A1CF07DDD002D76D0 /* Sources */, + 28F6B48B1CF07DDD002D76D0 /* Frameworks */, + 28F6B48C1CF07DDD002D76D0 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = libhfs; + productName = libhfs; + productReference = 28F6B48E1CF07DDD002D76D0 /* liblibhfs.a */; + productType = "com.apple.product-type.library.static"; + }; + 28F6B4A71CF07EC9002D76D0 /* libmfs */ = { + isa = PBXNativeTarget; + buildConfigurationList = 28F6B4AE1CF07EC9002D76D0 /* Build configuration list for PBXNativeTarget "libmfs" */; + buildPhases = ( + 28F6B4A41CF07EC9002D76D0 /* Sources */, + 28F6B4A51CF07EC9002D76D0 /* Frameworks */, + 28F6B4A61CF07EC9002D76D0 /* Copy Files */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = libmfs; + productName = libmfs; + productReference = 28F6B4A81CF07EC9002D76D0 /* liblibmfs.a */; + productType = "com.apple.product-type.library.static"; + }; + 28F6B4B51CF07F32002D76D0 /* libres */ = { + isa = PBXNativeTarget; + buildConfigurationList = 28F6B4BC1CF07F32002D76D0 /* Build configuration list for PBXNativeTarget "libres" */; + buildPhases = ( + 28F6B4B21CF07F32002D76D0 /* Sources */, + 28F6B4B31CF07F32002D76D0 /* Frameworks */, + 28F6B4B41CF07F32002D76D0 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = libres; + productName = libres; + productReference = 28F6B4B61CF07F32002D76D0 /* liblibres.a */; + productType = "com.apple.product-type.library.static"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -284,6 +526,15 @@ CreatedOnToolsVersion = 7.3; DevelopmentTeam = UJXNDZ5TNU; }; + 28F6B48D1CF07DDD002D76D0 = { + CreatedOnToolsVersion = 7.3.1; + }; + 28F6B4A71CF07EC9002D76D0 = { + CreatedOnToolsVersion = 7.3.1; + }; + 28F6B4B51CF07F32002D76D0 = { + CreatedOnToolsVersion = 7.3.1; + }; }; }; buildConfigurationList = 28F676B81CD15E0B00FC6FA6 /* Build configuration list for PBXProject "Mini vMac" */; @@ -300,6 +551,9 @@ projectRoot = ""; targets = ( 28F676BC1CD15E0B00FC6FA6 /* Mini vMac */, + 28F6B48D1CF07DDD002D76D0 /* libhfs */, + 28F6B4A71CF07EC9002D76D0 /* libmfs */, + 28F6B4B51CF07F32002D76D0 /* libres */, ); }; /* End PBXProject section */ @@ -338,6 +592,7 @@ 28CE8EB81CD4C3B200FE25A8 /* M68KITAB.c in Sources */, 28848B651CDE97E900B86C45 /* SettingsViewController.m in Sources */, 28BA89881CE73FBC00A98104 /* MNVMApplication.m in Sources */, + 28F6B4521CF07C48002D76D0 /* UIImage+DiskImageIcon.m in Sources */, 28CE8EB71CD4C3B200FE25A8 /* KBRDEMDV.c in Sources */, 28CE8EBC1CD4C3B200FE25A8 /* ROMEMDEV.c in Sources */, 28BA897F1CE7315400A98104 /* KBKeyboardLayout.m in Sources */, @@ -354,6 +609,42 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 28F6B48A1CF07DDD002D76D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28F6B49A1CF07E00002D76D0 /* file.c in Sources */, + 28F6B4981CF07E00002D76D0 /* btree.c in Sources */, + 28F6B49E1CF07E00002D76D0 /* memcmp.c in Sources */, + 28F6B49D1CF07E00002D76D0 /* medium.c in Sources */, + 28F6B4A11CF07E00002D76D0 /* version.c in Sources */, + 28F6B49C1CF07E00002D76D0 /* low.c in Sources */, + 28F6B49F1CF07E00002D76D0 /* node.c in Sources */, + 28F6B4991CF07E00002D76D0 /* data.c in Sources */, + 28F6B49B1CF07E00002D76D0 /* hfs.c in Sources */, + 28F6B4A21CF07E00002D76D0 /* volume.c in Sources */, + 28F6B4A01CF07E00002D76D0 /* record.c in Sources */, + 28F6B4A31CF07E08002D76D0 /* unix.c in Sources */, + 28F6B4971CF07E00002D76D0 /* block.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 28F6B4A41CF07EC9002D76D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28F6B4B11CF07ED9002D76D0 /* mfs.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 28F6B4B21CF07F32002D76D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28F6B4BF1CF07F39002D76D0 /* res.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ @@ -466,8 +757,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + HEADER_SEARCH_PATHS = "$(SRCROOT)"; INFOPLIST_FILE = "Mini vMac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_CFLAGS = "-DUSE_LIBRES"; PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -480,14 +773,80 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + HEADER_SEARCH_PATHS = "$(SRCROOT)"; INFOPLIST_FILE = "Mini vMac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_CFLAGS = "-DUSE_LIBRES"; PRODUCT_BUNDLE_IDENTIFIER = net.namedfork.minivmac; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; }; name = Release; }; + 28F6B4951CF07DDD002D76D0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = NO; + OTHER_CFLAGS = "-DHAVE_CONFIG_H"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 28F6B4961CF07DDD002D76D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = NO; + OTHER_CFLAGS = "-DHAVE_CONFIG_H"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 28F6B4AF1CF07EC9002D76D0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = NO; + HEADER_SEARCH_PATHS = "$(SRCROOT)"; + OTHER_CFLAGS = "-DUSE_LIBRES"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 28F6B4B01CF07EC9002D76D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = NO; + HEADER_SEARCH_PATHS = "$(SRCROOT)"; + OTHER_CFLAGS = "-DUSE_LIBRES"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 28F6B4BD1CF07F32002D76D0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 28F6B4BE1CF07F32002D76D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -509,6 +868,30 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 28F6B4941CF07DDD002D76D0 /* Build configuration list for PBXNativeTarget "libhfs" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 28F6B4951CF07DDD002D76D0 /* Debug */, + 28F6B4961CF07DDD002D76D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + 28F6B4AE1CF07EC9002D76D0 /* Build configuration list for PBXNativeTarget "libmfs" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 28F6B4AF1CF07EC9002D76D0 /* Debug */, + 28F6B4B01CF07EC9002D76D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + 28F6B4BC1CF07F32002D76D0 /* Build configuration list for PBXNativeTarget "libres" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 28F6B4BD1CF07F32002D76D0 /* Debug */, + 28F6B4BE1CF07F32002D76D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; /* End XCConfigurationList section */ }; rootObject = 28F676B51CD15E0B00FC6FA6 /* Project object */; diff --git a/Mini vMac/InsertDiskViewController.m b/Mini vMac/InsertDiskViewController.m index 2cd0fb1..376ad03 100644 --- a/Mini vMac/InsertDiskViewController.m +++ b/Mini vMac/InsertDiskViewController.m @@ -8,6 +8,7 @@ #import "InsertDiskViewController.h" #import "AppDelegate.h" +#import "UIImage+DiskImageIcon.h" @interface InsertDiskViewController () @@ -420,7 +421,15 @@ NSDictionary *attributes = [[NSURL fileURLWithPath:filePath] resourceValuesForKeys:@[NSURLTotalFileSizeKey] error:NULL]; if (attributes && attributes[NSURLTotalFileSizeKey]) { BOOL isDiskImage = [[AppDelegate sharedInstance].diskImageExtensions containsObject:fileName.pathExtension.lowercaseString]; - self.imageView.image = [UIImage imageNamed:isDiskImage ? @"floppy" : @"document"]; + if (isDiskImage) { + UIImage *icon = [UIImage imageWithIconForDiskImage:filePath]; + if (icon == nil) { + icon = [UIImage imageNamed:@"floppy"]; + } + self.imageView.image = icon; + } else { + self.imageView.image = [UIImage imageNamed:@"document"]; + } NSString *sizeString = [NSByteCountFormatter stringFromByteCount:[attributes[NSURLTotalFileSizeKey] longLongValue] countStyle:NSByteCountFormatterCountStyleBinary]; self.detailTextLabel.text = sizeString; } else { diff --git a/Mini vMac/UIImage+DiskImageIcon.h b/Mini vMac/UIImage+DiskImageIcon.h new file mode 100644 index 0000000..9e1d449 --- /dev/null +++ b/Mini vMac/UIImage+DiskImageIcon.h @@ -0,0 +1,15 @@ +// +// UIImage+DiskImageIcon.h +// Mini vMac +// +// Created by Jesús A. Álvarez on 21/05/2016. +// Copyright © 2016 namedfork. All rights reserved. +// + +#import + +@interface UIImage (DiskImageIcon) + ++ (UIImage *)imageWithIconForDiskImage:(NSString *)path; + +@end diff --git a/Mini vMac/UIImage+DiskImageIcon.m b/Mini vMac/UIImage+DiskImageIcon.m new file mode 100644 index 0000000..6ba6451 --- /dev/null +++ b/Mini vMac/UIImage+DiskImageIcon.m @@ -0,0 +1,602 @@ +// +// UIImage+DiskImageIcon.m +// Mini vMac +// +// Created by Jesús A. Álvarez on 21/05/2016. +// Copyright © 2016 namedfork. All rights reserved. +// + +#import "UIImage+DiskImageIcon.h" +#import "libhfs.h" +#import "res.h" +#import "mfs.h" + +#define kDiskImageHasDC42Header 1 << 0 +#define RSHORT(base, offset) ntohs(*((short *)((base) + (offset)))) +#define RLONG(base, offset) ntohl(*((long *)((base) + (offset)))) +#define RCSTR(base, offset) ((char *)((base) + (offset))) + +@interface DiskImageIconReader : NSObject + +- (UIImage *)iconForDiskImage:(NSString *)path; + +@end + +@implementation UIImage (DiskImageIcon) + ++ (UIImage *)imageWithIconForDiskImage:(NSString *)path { + return [[DiskImageIconReader new] iconForDiskImage:path]; +} + +@end + +// Mac OS 1 bit palette +static uint32_t ctb1[2] = {0xFFFFFF, 0x000000}; + +// Mac OS 4 bit palette +static uint32_t ctb4[16] = { + 0xFFFFFF, 0xFFFF00, 0xFF6600, 0xDD0000, 0xFF0099, 0x330099, 0x0000DD, 0x0099FF, + 0x00BB00, 0x006600, 0x663300, 0x996633, 0xCCCCCC, 0x888888, 0x444444, 0x000000}; +// Mac OS 8 bit palette +static uint32_t ctb8[256] = { + 0xFFFFFF, 0xFFFFCC, 0xFFFF99, 0xFFFF66, 0xFFFF33, 0xFFFF00, 0xFFCCFF, 0xFFCCCC, + 0xFFCC99, 0xFFCC66, 0xFFCC33, 0xFFCC00, 0xFF99FF, 0xFF99CC, 0xFF9999, 0xFF9966, + 0xFF9933, 0xFF9900, 0xFF66FF, 0xFF66CC, 0xFF6699, 0xFF6666, 0xFF6633, 0xFF6600, + 0xFF33FF, 0xFF33CC, 0xFF3399, 0xFF3366, 0xFF3333, 0xFF3300, 0xFF00FF, 0xFF00CC, + 0xFF0099, 0xFF0066, 0xFF0033, 0xFF0000, 0xCCFFFF, 0xCCFFCC, 0xCCFF99, 0xCCFF66, + 0xCCFF33, 0xCCFF00, 0xCCCCFF, 0xCCCCCC, 0xCCCC99, 0xCCCC66, 0xCCCC33, 0xCCCC00, + 0xCC99FF, 0xCC99CC, 0xCC9999, 0xCC9966, 0xCC9933, 0xCC9900, 0xCC66FF, 0xCC66CC, + 0xCC6699, 0xCC6666, 0xCC6633, 0xCC6600, 0xCC33FF, 0xCC33CC, 0xCC3399, 0xCC3366, + 0xCC3333, 0xCC3300, 0xCC00FF, 0xCC00CC, 0xCC0099, 0xCC0066, 0xCC0033, 0xCC0000, + 0x99FFFF, 0x99FFCC, 0x99FF99, 0x99FF66, 0x99FF33, 0x99FF00, 0x99CCFF, 0x99CCCC, + 0x99CC99, 0x99CC66, 0x99CC33, 0x99CC00, 0x9999FF, 0x9999CC, 0x999999, 0x999966, + 0x999933, 0x999900, 0x9966FF, 0x9966CC, 0x996699, 0x996666, 0x996633, 0x996600, + 0x9933FF, 0x9933CC, 0x993399, 0x993366, 0x993333, 0x993300, 0x9900FF, 0x9900CC, + 0x990099, 0x990066, 0x990033, 0x990000, 0x66FFFF, 0x66FFCC, 0x66FF99, 0x66FF66, + 0x66FF33, 0x66FF00, 0x66CCFF, 0x66CCCC, 0x66CC99, 0x66CC66, 0x66CC33, 0x66CC00, + 0x6699FF, 0x6699CC, 0x669999, 0x669966, 0x669933, 0x669900, 0x6666FF, 0x6666CC, + 0x666699, 0x666666, 0x666633, 0x666600, 0x6633FF, 0x6633CC, 0x663399, 0x663366, + 0x663333, 0x663300, 0x6600FF, 0x6600CC, 0x660099, 0x660066, 0x660033, 0x660000, + 0x33FFFF, 0x33FFCC, 0x33FF99, 0x33FF66, 0x33FF33, 0x33FF00, 0x33CCFF, 0x33CCCC, + 0x33CC99, 0x33CC66, 0x33CC33, 0x33CC00, 0x3399FF, 0x3399CC, 0x339999, 0x339966, + 0x339933, 0x339900, 0x3366FF, 0x3366CC, 0x336699, 0x336666, 0x336633, 0x336600, + 0x3333FF, 0x3333CC, 0x333399, 0x333366, 0x333333, 0x333300, 0x3300FF, 0x3300CC, + 0x330099, 0x330066, 0x330033, 0x330000, 0x00FFFF, 0x00FFCC, 0x00FF99, 0x00FF66, + 0x00FF33, 0x00FF00, 0x00CCFF, 0x00CCCC, 0x00CC99, 0x00CC66, 0x00CC33, 0x00CC00, + 0x0099FF, 0x0099CC, 0x009999, 0x009966, 0x009933, 0x009900, 0x0066FF, 0x0066CC, + 0x006699, 0x006666, 0x006633, 0x006600, 0x0033FF, 0x0033CC, 0x003399, 0x003366, + 0x003333, 0x003300, 0x0000FF, 0x0000CC, 0x000099, 0x000066, 0x000033, 0xEE0000, + 0xDD0000, 0xBB0000, 0xAA0000, 0x880000, 0x770000, 0x550000, 0x440000, 0x220000, + 0x110000, 0x00EE00, 0x00DD00, 0x00BB00, 0x00AA00, 0x008800, 0x007700, 0x005500, + 0x004400, 0x002200, 0x001100, 0x0000EE, 0x0000DD, 0x0000BB, 0x0000AA, 0x000088, + 0x000077, 0x000055, 0x000044, 0x000022, 0x000011, 0xEEEEEE, 0xDDDDDD, 0xBBBBBB, + 0xAAAAAA, 0x888888, 0x777777, 0x555555, 0x444444, 0x222222, 0x111111, 0x000000}; + +@implementation DiskImageIconReader + +- (UIImage *)iconForDiskImage:(NSString *)path { + // determine format and offset of disk image + NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:path]; + if (fh == nil) { + return nil; + } + [fh seekToFileOffset:1024]; + NSData *checkHeader = [fh readDataOfLength:128]; + [fh closeFile]; + const unsigned char *chb = [checkHeader bytes]; + + // determine type from header + if ((chb[0] == 0x42) && (chb[1] == 0x44)) { + /* hfs */ + return [self iconForHFSDiskImage:path options:0]; + } else if ((chb[0] == 0xD2) && (chb[1] == 0xD7)) { + /* mfs */ + return [self iconForMFSDiskImage:path options:0]; + } else if ((chb[84] == 0x42) && (chb[85] == 0x44)) { + /* hfs, dc42 header */ + return [self iconForHFSDiskImage:path options:kDiskImageHasDC42Header]; + } else if ((chb[84] == 0xD2) && (chb[85] == 0xD7)) { + /* mfs, dc42 header */ + return [self iconForMFSDiskImage:path options:kDiskImageHasDC42Header]; + } + + return nil; +} + +#pragma mark - MFS + +- (UIImage *)iconForMFSDiskImage:(NSString *)path options:(int)options { + // open disk image + size_t offset = (options & kDiskImageHasDC42Header) ? 84 : 0; + MFSVolume *vol = mfs_vopen([path fileSystemRepresentation], (size_t)offset, 0); + if (vol == NULL) { + NSLog(@"Can't open MFS volume at %@", path); + return nil; + } + NSString *volName = [NSString stringWithCString:vol->name encoding:NSMacOSRomanStringEncoding]; + NSString *volComment; + char *const volCommentBytes = mfs_comment(vol, NULL); + if (volCommentBytes) { + volComment = [NSString stringWithCString:volCommentBytes encoding:NSMacOSRomanStringEncoding]; + free(volCommentBytes); + } + + // find applications + MFSDirectoryRecord *rec; + NSMutableArray *apps = [NSMutableArray arrayWithCapacity:5]; + for (int i = 0; vol->directory[i]; i++) { + rec = vol->directory[i]; + if (ntohl(rec->flUsrWds.type) != 'APPL') { + continue; + } + [apps addObject:[NSNumber numberWithInt:i]]; + } + + // if there's more than one app, find one that looks matching + if ([apps count] == 0) { + return nil; + } else if ([apps count] > 1) { + rec = NULL; + for (NSNumber *num in apps) { + rec = vol->directory[[num intValue]]; + NSString *appName = [[NSString alloc] initWithCString:rec->flCName encoding:NSMacOSRomanStringEncoding]; + if (![self chooseApp:appName inVolume:volName hint:volComment]) { + rec = NULL; + } + if (rec) { + break; + } + } + if (rec == NULL) { + return nil; + } + } else { + rec = vol->directory[[[apps objectAtIndex:0] intValue]]; + } + + // open resource fork + MFSFork *rsrcFork = mfs_fkopen(vol, rec, kMFSForkRsrc, 0); + RFILE *rfile = res_open_funcs(rsrcFork, mfs_fkseek, mfs_fkread); + + // get icon + CGImageRef iconImage = [self appIconForResourceFile:rfile creator:ntohl(rec->flUsrWds.creator)]; + UIImage *icon = nil; + if (iconImage) { + icon = [UIImage imageWithCGImage:iconImage]; + CGImageRelease(iconImage); + } + + // close stuff + res_close(rfile); + mfs_fkclose(rsrcFork); + mfs_vclose(vol); + + return icon; +} + +#pragma mark - HFS + +- (UIImage *)iconForHFSDiskImage:(NSString *)path options:(int)options { + // open disk image + int mountFlags = HFS_MODE_RDONLY; + if (options & kDiskImageHasDC42Header) { + mountFlags |= HFS_OPT_DC42HEADER; + } + hfsvol *vol = hfs_mount([path fileSystemRepresentation], 0, mountFlags); + if (vol == NULL) { + NSLog(@"Can't open HFS volume at %@ with flags %x", path, mountFlags); + return nil; + } + + // try volume icon + UIImage *volumeIcon = [self iconFromHFSVolumeIcon:vol]; + if (volumeIcon) { + hfs_umount(vol); + return volumeIcon; + } + + // find best application + UIImage *icon = nil; + NSString *appPath = [self findAppInHFSVolume:vol]; + hfsfile *hfile = NULL; + RFILE *rfile = NULL; + if (appPath == nil) { + hfs_umount(vol); + return nil; + } + + // open resource fork + hfile = hfs_open(vol, [appPath cStringUsingEncoding:NSMacOSRomanStringEncoding]); + if (hfile == NULL) { + hfs_umount(vol); + return nil; + } + hfs_setfork(hfile, 1); + rfile = res_open_funcs(hfile, (res_seek_func)hfs_seek, (res_read_func)hfs_read); + if (rfile == NULL) { + hfs_close(hfile); + hfs_umount(vol); + return nil; + } + + // get icon + hfsdirent ent; + if (hfs_stat(vol, [appPath cStringUsingEncoding:NSMacOSRomanStringEncoding], &ent)) { + res_close(rfile); + hfs_close(hfile); + hfs_umount(vol); + return nil; + } + CGImageRef iconImage = [self appIconForResourceFile:rfile creator:ntohl(*(uint32_t *)ent.u.file.creator)]; + if (iconImage) { + icon = [UIImage imageWithCGImage:iconImage]; + CGImageRelease(iconImage); + } + + // close stuff + res_close(rfile); + hfs_close(hfile); + hfs_umount(vol); + return icon; +} + +- (NSString *)findAppInHFSVolume:(hfsvol *)vol { + // get disk name + hfsvolent volEnt; + hfs_vstat(vol, &volEnt); + NSString *volName = [NSString stringWithCString:volEnt.name encoding:NSMacOSRomanStringEncoding]; + NSString *volComment = [self commentForHFSVolume:vol]; + + // find apps + NSMutableArray *apps = [[NSMutableArray alloc] initWithCapacity:5]; + [self findApps:apps inDirectory:HFS_CNID_ROOTDIR ofHFSVolume:vol skipFolder:volEnt.blessed]; + + // decide which one to use + NSString *myApp = nil; + NSString *appName = nil; + if ([apps count] == 1) { + myApp = [apps objectAtIndex:0]; + } else if ([apps count] > 1) { + for (NSString *appPath in apps) { + // choose an app + appName = [appPath componentsSeparatedByString:@":"].lastObject; + if (![self chooseApp:appName inVolume:volName hint:volComment]) { + continue; + } + myApp = appPath; + } + } + + return myApp; +} + +- (void)findApps:(NSMutableArray *)apps inDirectory:(unsigned long)cnid ofHFSVolume:(hfsvol *)vol skipFolder:(unsigned long)skipCNID { + if (hfs_setcwd(vol, cnid)) { + return; + } + hfsdir *dir = hfs_opendir(vol, ":"); + if (dir == NULL) { + return; + } + hfsdirent ent; + while (hfs_readdir(dir, &ent) == 0) { + if (ent.flags & HFS_ISDIR && ent.cnid != skipCNID) { + [self findApps:apps inDirectory:ent.cnid ofHFSVolume:vol skipFolder:skipCNID]; + } else if (ntohl(*(uint32_t *)ent.u.file.type) == 'APPL') { + // Found an app + [apps addObject:[self pathToDirEntry:&ent ofHFSVolume:vol]]; + } + } + hfs_closedir(dir); +} + +- (NSString *)pathToDirEntry:(const hfsdirent *)ent ofHFSVolume:(hfsvol *)vol { + NSMutableString *path = [NSMutableString stringWithCString:ent->name encoding:NSMacOSRomanStringEncoding]; + NSString *entName; + char name[HFS_MAX_FLEN + 1]; + unsigned long cnid = ent->parid; + while (cnid != HFS_CNID_ROOTPAR) { + if (hfs_dirinfo(vol, &cnid, name)) { + return nil; + } + entName = [[NSString alloc] initWithCString:name encoding:NSMacOSRomanStringEncoding]; + [path insertString:@":" atIndex:0]; + [path insertString:entName atIndex:0]; + } + return path; +} + +- (NSString *)commentForHFSVolume:(hfsvol *)vol { + hfsvolent vent; + hfsdirent dent; + NSString *comment = nil; + + // get comment ID + if (hfs_vstat(vol, &vent) || hfs_stat(vol, ":", &dent)) { + return nil; + } + unsigned short cmtID = dent.fdcomment; + + // open desktop + hfsfile *hfile = NULL; + RFILE *rfile = NULL; + hfs_chdir(vol, vent.name); + hfile = hfs_open(vol, "Desktop"); + if (hfile == NULL) { + return nil; + } + hfs_setfork(hfile, 1); + rfile = res_open_funcs(hfile, (res_seek_func)hfs_seek, (res_read_func)hfs_read); + if (rfile == NULL) { + hfs_close(hfile); + return nil; + } + + // read resource + unsigned char cmtLen; + size_t readBytes; + res_read(rfile, 'FCMT', cmtID, &cmtLen, 0, 1, &readBytes, NULL); + if (readBytes == 0) { + res_close(rfile); + hfs_close(hfile); + return nil; + } + char cmtBytes[256]; + res_read(rfile, 'FCMT', cmtID, cmtBytes, 1, cmtLen, &readBytes, NULL); + cmtBytes[cmtLen] = '\0'; + comment = [NSString stringWithCString:cmtBytes encoding:NSMacOSRomanStringEncoding]; + + // close + res_close(rfile); + hfs_close(hfile); + return comment; +} + +- (UIImage *)iconFromHFSVolumeIcon:(hfsvol *)vol { + UIImage *icon = nil; + hfsvolent vent; + if (hfs_vstat(vol, &vent)) { + return nil; + } + + // open icon file + hfs_chdir(vol, vent.name); + hfsfile *hfile = NULL; + RFILE *rfile = NULL; + hfile = hfs_open(vol, "Icon\x0D"); + if (hfile == NULL) { + res_close(rfile); + return nil; + } + hfs_setfork(hfile, 1); + rfile = res_open_funcs(hfile, (res_seek_func)hfs_seek, (res_read_func)hfs_read); + if (rfile == NULL) { + if (hfile) { + hfs_close(hfile); + } + return nil; + } + // read icon family + NSDictionary *iconFamily = [self iconFamilyID:-16455 inResourceFile:rfile]; + + // create image + CGImageRef iconImage = [self iconImageFromFamily:iconFamily]; + if (iconImage) { + icon = [UIImage imageWithCGImage:iconImage]; + CGImageRelease(iconImage); + } + + res_close(rfile); + if (hfile) { + hfs_close(hfile); + } + return icon; +} + +#pragma mark - App Selection + +- (BOOL)chooseApp:(NSString *)appName inVolume:(NSString *)volName hint:(NSString *)hint { + return ([appName hasPrefix:volName] || + [volName hasPrefix:appName] || + [volName isEqualToString:appName] || + [appName isEqualToString:hint]); +} + +#pragma mark - Resource Access + +- (CGImageRef)appIconForResourceFile:(RFILE *)rfile creator:(OSType)creator { + // load bundle + size_t numBundles; + ResAttr *bundles = res_list(rfile, 'BNDL', NULL, 0, 0, &numBundles, NULL); + void *bundle = NULL; + if (numBundles == 0 || bundles == NULL) { + return nil; + } + for (int i = 0; i < numBundles; i++) { + bundle = res_read(rfile, 'BNDL', bundles[i].ID, NULL, 0, 0, NULL, NULL); + if (bundle == NULL || ntohl(*(OSType *)bundle) == creator) { + break; + } + free(bundle); + bundle = NULL; + } + free(bundles); + if (bundle == NULL) { + return nil; + } + + // read bundle + int iconID = [self iconFamilyIDForType:'APPL' inBundle:bundle inResourceFile:rfile]; + free(bundle); + if (iconID == NSNotFound) { + return nil; + } + + // read icon family + NSDictionary *iconFamily = [self iconFamilyID:iconID inResourceFile:rfile]; + + // create image + return [self iconImageFromFamily:iconFamily]; +} + +- (NSDictionary *)iconFamilyID:(int16_t)famID inResourceFile:(RFILE *)rfile { + NSMutableDictionary *iconFamily = [NSMutableDictionary dictionaryWithCapacity:6]; + NSData *iconData, *maskData; + void *iconRsrc; + size_t resSize; + + // separate resources + const uint32_t iconResourceTypes[] = {'ICN#', 'icl4', 'icl8', 'ics#', 'ics4', 'ics8', 0}; + for (int i = 0; iconResourceTypes[i]; i++) { + iconRsrc = res_read(rfile, iconResourceTypes[i], famID, NULL, 0, 0, &resSize, NULL); + if (iconRsrc == NULL) { + continue; + } + [iconFamily setObject:[NSData dataWithBytes:iconRsrc length:resSize] forKey:[NSString stringWithFormat:@"%c%c%c%c", TYPECHARS(iconResourceTypes[i])]]; + free(iconRsrc); + } + + // mask pseudo-resources + if ((iconData = [iconFamily objectForKey:@"ICN#"])) { + maskData = [iconData subdataWithRange:NSMakeRange(0x80, 0x80)]; + [iconFamily setObject:maskData forKey:@"IMK#"]; + } + if ((iconData = [iconFamily objectForKey:@"ics#"])) { + maskData = [iconData subdataWithRange:NSMakeRange(0x20, 0x20)]; + [iconFamily setObject:maskData forKey:@"imk#"]; + } + + return iconFamily; +} + +- (int)iconFamilyIDForType:(OSType)type inBundle:(void *)bndl inResourceFile:(RFILE *)rfile { + short numIconFamilies = RSHORT(bndl, 0x0C) + 1; + short *iconFamily = (short *)(bndl + 0x0E); + short numFileRefs = RSHORT(bndl, (numIconFamilies * 4) + 0x12) + 1; + short *fileRef = (short *)(bndl + (numIconFamilies * 4) + 0x14); + + // find FREF for APPL type + short localIconID; + void *FREF = NULL; + for (int i = 0; i < 2 * numFileRefs; i += 2) { + FREF = res_read(rfile, 'FREF', (int)ntohs(fileRef[i + 1]), NULL, 0, 0, NULL, NULL); + if (FREF == NULL || RLONG(FREF, 0) == 'APPL') { + break; + } + free(FREF); + FREF = NULL; + } + if (FREF == NULL) { + return NSNotFound; + } + + // read FREF + localIconID = RSHORT(FREF, 4); + free(FREF); + + // find resource ID for local ID + for (int i = 0; i < 2 * numIconFamilies; i += 2) { + if (ntohs(iconFamily[i]) == localIconID) { + return (int)ntohs(iconFamily[i + 1]); + } + } + + return NSNotFound; +} + +- (CGImageRef)iconImageFromFamily:(NSDictionary *)iconFamily { + NSData *iconData, *iconMask; + if ((iconMask = [iconFamily objectForKey:@"IMK#"])) { + // has large mask, find best large icon + if ((iconData = [iconFamily objectForKey:@"icl8"])) { + return [self iconImageWithData:iconData mask:iconMask size:32 depth:8]; + } else if ((iconData = [iconFamily objectForKey:@"icl4"])) { + return [self iconImageWithData:iconData mask:iconMask size:32 depth:4]; + } else { + iconData = [iconFamily objectForKey:@"ICN#"]; + } + return [self iconImageWithData:iconData mask:iconMask size:32 depth:1]; + } else if ((iconMask = [iconFamily objectForKey:@"imk#"])) { + // has small mask, find best small icon + if ((iconData = [iconFamily objectForKey:@"ics8"])) { + return [self iconImageWithData:iconData mask:iconMask size:32 depth:8]; + } else if ((iconData = [iconFamily objectForKey:@"ics4"])) { + return [self iconImageWithData:iconData mask:iconMask size:32 depth:4]; + } else { + iconData = [iconFamily objectForKey:@"ics#"]; + } + return [self iconImageWithData:iconData mask:iconMask size:32 depth:1]; + } + return NULL; +} + +- (CGImageRef)iconImageWithData:(NSData *)iconData mask:(NSData *)iconMask size:(int)size depth:(int)depth { + if (iconData == nil || iconMask == nil) { + return NULL; + } + + // convert to ARGB + #define _iSETPIXELRGB(px, py, sa, srgb) \ + data[(4 * (px + (py * size))) + 0] = sa; \ + data[(4 * (px + (py * size))) + 1] = ((srgb >> 16) & 0xFF); \ + data[(4 * (px + (py * size))) + 2] = ((srgb >> 8) & 0xFF); \ + data[(4 * (px + (py * size))) + 3] = (srgb & 0xFF) + + CFMutableDataRef pixels = CFDataCreateMutable(kCFAllocatorDefault, 4 * size * size); + CFDataSetLength(pixels, 4 * size * size); + unsigned char *data = CFDataGetMutableBytePtr(pixels); + const unsigned char *pixelData = [iconData bytes]; + const unsigned char *maskData = [iconMask bytes]; + int m, mxy, pxy, rgb; + if (pixels == NULL) { + return NULL; + } + switch (depth) { + case 1: + // 1-bit + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + mxy = pxy = (y * (size / 8)) + (x / 8); + m = ((maskData[mxy] >> (7 - (x % 8))) & 0x01) ? 0xFF : 0x00; + rgb = ctb1[((pixelData[pxy] >> (7 - (x % 8))) & 0x01)]; + _iSETPIXELRGB(x, y, m, rgb); + } + } + break; + case 4: + // 4-bit + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + mxy = (y * (size / 8)) + (x / 8); + pxy = (y * (size / 2)) + (x / 2); + m = ((maskData[mxy] >> (7 - (x % 8))) & 0x01) ? 0xFF : 0x00; + rgb = ctb4[(pixelData[pxy] >> 4 * (1 - x % 2)) & 0x0F]; + _iSETPIXELRGB(x, y, m, rgb); + } + } + break; + case 8: + // 8-bit + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + mxy = (y * (size / 8)) + (x / 8); + pxy = (y * size) + x; + m = ((maskData[mxy] >> (7 - (x % 8))) & 0x01) ? 0xFF : 0x00; + rgb = ctb8[pixelData[pxy]]; + _iSETPIXELRGB(x, y, m, rgb); + } + } + break; + } + + // create image + CGDataProviderRef provider = CGDataProviderCreateWithCFData(pixels); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGImageRef image = CGImageCreate(size, size, 8, 32, size * 4, colorSpace, kCGImageAlphaFirst | kCGBitmapByteOrder32Big, provider, NULL, false, kCGRenderingIntentDefault); + CGDataProviderRelease(provider); + CGColorSpaceRelease(colorSpace); + CFRelease(pixels); + return image; +} + +@end \ No newline at end of file diff --git a/libhfs/acconfig.h b/libhfs/acconfig.h new file mode 100644 index 0000000..a4e50ba --- /dev/null +++ b/libhfs/acconfig.h @@ -0,0 +1,38 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: acconfig.h,v 1.5 1998/04/11 08:27:11 rob Exp $ + */ + +/***************************************************************************** + * Definitions selected automatically by `configure' * + *****************************************************************************/ +@TOP@ + +/* Define if you want to enable diagnostic debugging support. */ +#undef DEBUG + +@BOTTOM@ + +/***************************************************************************** + * End of automatically configured definitions * + *****************************************************************************/ + +# ifdef DEBUG +# include +# endif diff --git a/libhfs/apple.h b/libhfs/apple.h new file mode 100644 index 0000000..d2bb998 --- /dev/null +++ b/libhfs/apple.h @@ -0,0 +1,280 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: apple.h,v 1.1 1998/04/11 08:27:11 rob Exp $ + */ + +#if defined(__APPLE__) +#define Str15 _Str15 +#define Str31 _Str31 +#define OSType _OSType +#define Rect _Rect +#define Point _Point +#endif + +typedef signed char Char; +typedef unsigned char UChar; +typedef signed char SignedByte; +typedef signed short Integer; +typedef unsigned short UInteger; +typedef signed long LongInt; +typedef unsigned long ULongInt; +typedef char Str15[16]; +typedef char Str31[32]; +typedef long OSType; + +typedef struct { + Integer sbSig; /* device signature (should be 0x4552) */ + Integer sbBlkSize; /* block size of the device (in bytes) */ + LongInt sbBlkCount; /* number of blocks on the device */ + Integer sbDevType; /* reserved */ + Integer sbDevId; /* reserved */ + LongInt sbData; /* reserved */ + Integer sbDrvrCount; /* number of driver descriptor entries */ + LongInt ddBlock; /* first driver's starting block */ + Integer ddSize; /* size of the driver, in 512-byte blocks */ + Integer ddType; /* driver operating system type (MacOS = 1) */ + Integer ddPad[243]; /* additional drivers, if any */ +} Block0; + +typedef struct { + Integer pmSig; /* partition signature (0x504d or 0x5453) */ + Integer pmSigPad; /* reserved */ + LongInt pmMapBlkCnt; /* number of blocks in partition map */ + LongInt pmPyPartStart; /* first physical block of partition */ + LongInt pmPartBlkCnt; /* number of blocks in partition */ + Char pmPartName[33]; /* partition name */ + Char pmParType[33]; /* partition type */ + LongInt pmLgDataStart; /* first logical block of data area */ + LongInt pmDataCnt; /* number of blocks in data area */ + LongInt pmPartStatus; /* partition status information */ + LongInt pmLgBootStart; /* first logical block of boot code */ + LongInt pmBootSize; /* size of boot code, in bytes */ + LongInt pmBootAddr; /* boot code load address */ + LongInt pmBootAddr2; /* reserved */ + LongInt pmBootEntry; /* boot code entry point */ + LongInt pmBootEntry2; /* reserved */ + LongInt pmBootCksum; /* boot code checksum */ + Char pmProcessor[17];/* processor type */ + Integer pmPad[188]; /* reserved */ +} Partition; + +typedef struct { + Integer bbID; /* boot blocks signature */ + LongInt bbEntry; /* entry point to boot code */ + Integer bbVersion; /* boot blocks version number */ + Integer bbPageFlags; /* used internally */ + Str15 bbSysName; /* System filename */ + Str15 bbShellName; /* Finder filename */ + Str15 bbDbg1Name; /* debugger filename */ + Str15 bbDbg2Name; /* debugger filename */ + Str15 bbScreenName; /* name of startup screen */ + Str15 bbHelloName; /* name of startup program */ + Str15 bbScrapName; /* name of system scrap file */ + Integer bbCntFCBs; /* number of FCBs to allocate */ + Integer bbCntEvts; /* number of event queue elements */ + LongInt bb128KSHeap; /* system heap size on 128K Mac */ + LongInt bb256KSHeap; /* used internally */ + LongInt bbSysHeapSize; /* system heap size on all machines */ + Integer filler; /* reserved */ + LongInt bbSysHeapExtra; /* additional system heap space */ + LongInt bbSysHeapFract; /* fraction of RAM for system heap */ +} BootBlkHdr; + +typedef struct { + UInteger xdrStABN; /* first allocation block */ + UInteger xdrNumABlks; /* number of allocation blocks */ +} ExtDescriptor; + +typedef ExtDescriptor ExtDataRec[3]; + +typedef struct { + SignedByte xkrKeyLen; /* key length */ + SignedByte xkrFkType; /* fork type (0x00/0xff == data/resource */ + ULongInt xkrFNum; /* file number */ + UInteger xkrFABN; /* starting file allocation block */ +} ExtKeyRec; + +typedef struct { + SignedByte ckrKeyLen; /* key length */ + SignedByte ckrResrv1; /* reserved */ + ULongInt ckrParID; /* parent directory ID */ + Str31 ckrCName; /* catalog node name */ +} CatKeyRec; + +typedef struct { + Integer v; /* vertical coordinate */ + Integer h; /* horizontal coordinate */ +} Point; + +typedef struct { + Integer top; /* top edge of rectangle */ + Integer left; /* left edge */ + Integer bottom; /* bottom edge */ + Integer right; /* right edge */ +} Rect; + +typedef struct { + Rect frRect; /* folder's rectangle */ + Integer frFlags; /* flags */ + Point frLocation; /* folder's location */ + Integer frView; /* folder's view */ +} DInfo; + +typedef struct { + Point frScroll; /* scroll position */ + LongInt frOpenChain; /* directory ID chain of open folders */ + Integer frUnused; /* reserved */ + Integer frComment; /* comment ID */ + LongInt frPutAway; /* directory ID */ +} DXInfo; + +typedef struct { + OSType fdType; /* file type */ + OSType fdCreator; /* file's creator */ + Integer fdFlags; /* flags */ + Point fdLocation; /* file's location */ + Integer fdFldr; /* file's window */ +} FInfo; + +typedef struct { + Integer fdIconID; /* icon ID */ + Integer fdUnused[4]; /* reserved */ + Integer fdComment; /* comment ID */ + LongInt fdPutAway; /* home directory ID */ +} FXInfo; + +typedef struct { + Integer drSigWord; /* volume signature (0x4244 for HFS) */ + LongInt drCrDate; /* date and time of volume creation */ + LongInt drLsMod; /* date and time of last modification */ + Integer drAtrb; /* volume attributes */ + UInteger drNmFls; /* number of files in root directory */ + UInteger drVBMSt; /* first block of volume bit map (always 3) */ + UInteger drAllocPtr; /* start of next allocation search */ + UInteger drNmAlBlks; /* number of allocation blocks in volume */ + ULongInt drAlBlkSiz; /* size (in bytes) of allocation blocks */ + ULongInt drClpSiz; /* default clump size */ + UInteger drAlBlSt; /* first allocation block in volume */ + LongInt drNxtCNID; /* next unused catalog node ID (dir/file ID) */ + UInteger drFreeBks; /* number of unused allocation blocks */ + char drVN[28]; /* volume name (1-27 chars) */ + LongInt drVolBkUp; /* date and time of last backup */ + Integer drVSeqNum; /* volume backup sequence number */ + ULongInt drWrCnt; /* volume write count */ + ULongInt drXTClpSiz; /* clump size for extents overflow file */ + ULongInt drCTClpSiz; /* clump size for catalog file */ + UInteger drNmRtDirs; /* number of directories in root directory */ + ULongInt drFilCnt; /* number of files in volume */ + ULongInt drDirCnt; /* number of directories in volume */ + LongInt drFndrInfo[8]; /* information used by the Finder */ + UInteger drEmbedSigWord; /* type of embedded volume */ + ExtDescriptor drEmbedExtent; /* location of embedded volume */ + ULongInt drXTFlSize; /* size (in bytes) of extents overflow file */ + ExtDataRec drXTExtRec; /* first extent record for extents file */ + ULongInt drCTFlSize; /* size (in bytes) of catalog file */ + ExtDataRec drCTExtRec; /* first extent record for catalog file */ +} MDB; + +typedef enum { + cdrDirRec = 1, + cdrFilRec = 2, + cdrThdRec = 3, + cdrFThdRec = 4 +} CatDataType; + +typedef struct { + SignedByte cdrType; /* record type */ + SignedByte cdrResrv2; /* reserved */ + union { + struct { /* cdrDirRec */ + Integer dirFlags; /* directory flags */ + UInteger dirVal; /* directory valence */ + ULongInt dirDirID; /* directory ID */ + LongInt dirCrDat; /* date and time of creation */ + LongInt dirMdDat; /* date and time of last modification */ + LongInt dirBkDat; /* date and time of last backup */ + DInfo dirUsrInfo; /* Finder information */ + DXInfo dirFndrInfo; /* additional Finder information */ + LongInt dirResrv[4]; /* reserved */ + } dir; + struct { /* cdrFilRec */ + SignedByte + filFlags; /* file flags */ + SignedByte + filTyp; /* file type */ + FInfo filUsrWds; /* Finder information */ + ULongInt filFlNum; /* file ID */ + UInteger filStBlk; /* first alloc block of data fork */ + ULongInt filLgLen; /* logical EOF of data fork */ + ULongInt filPyLen; /* physical EOF of data fork */ + UInteger filRStBlk; /* first alloc block of resource fork */ + ULongInt filRLgLen; /* logical EOF of resource fork */ + ULongInt filRPyLen; /* physical EOF of resource fork */ + LongInt filCrDat; /* date and time of creation */ + LongInt filMdDat; /* date and time of last modification */ + LongInt filBkDat; /* date and time of last backup */ + FXInfo filFndrInfo; /* additional Finder information */ + UInteger filClpSize; /* file clump size */ + ExtDataRec + filExtRec; /* first data fork extent record */ + ExtDataRec + filRExtRec; /* first resource fork extent record */ + LongInt filResrv; /* reserved */ + } fil; + struct { /* cdrThdRec */ + LongInt thdResrv[2]; /* reserved */ + ULongInt thdParID; /* parent ID for this directory */ + Str31 thdCName; /* name of this directory */ + } dthd; + struct { /* cdrFThdRec */ + LongInt fthdResrv[2]; /* reserved */ + ULongInt fthdParID; /* parent ID for this file */ + Str31 fthdCName; /* name of this file */ + } fthd; + } u; +} CatDataRec; + +typedef struct { + ULongInt ndFLink; /* forward link */ + ULongInt ndBLink; /* backward link */ + SignedByte ndType; /* node type */ + SignedByte ndNHeight; /* node level */ + UInteger ndNRecs; /* number of records in node */ + Integer ndResv2; /* reserved */ +} NodeDescriptor; + +enum { + ndIndxNode = (SignedByte) 0x00, + ndHdrNode = (SignedByte) 0x01, + ndMapNode = (SignedByte) 0x02, + ndLeafNode = (SignedByte) 0xff +}; + +typedef struct { + UInteger bthDepth; /* current depth of tree */ + ULongInt bthRoot; /* number of root node */ + ULongInt bthNRecs; /* number of leaf records in tree */ + ULongInt bthFNode; /* number of first leaf node */ + ULongInt bthLNode; /* number of last leaf node */ + UInteger bthNodeSize; /* size of a node */ + UInteger bthKeyLen; /* maximum length of a key */ + ULongInt bthNNodes; /* total number of nodes in tree */ + ULongInt bthFree; /* number of free nodes */ + SignedByte bthResv[76]; /* reserved */ +} BTHdrRec; diff --git a/libhfs/block.c b/libhfs/block.c new file mode 100644 index 0000000..085451a --- /dev/null +++ b/libhfs/block.c @@ -0,0 +1,807 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: block.c,v 1.11 1998/11/02 22:08:52 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include + +# include "libhfs.h" +# include "volume.h" +# include "block.h" +# include "os.h" + +# define INUSE(b) ((b)->flags & HFS_BUCKET_INUSE) +# define DIRTY(b) ((b)->flags & HFS_BUCKET_DIRTY) + +/* + * NAME: block->init() + * DESCRIPTION: initialize a volume's block cache + */ +int b_init(hfsvol *vol) +{ + bcache *cache; + int i; + + ASSERT(vol->cache == 0); + + cache = ALLOC(bcache, 1); + if (cache == 0) + ERROR(ENOMEM, 0); + + vol->cache = cache; + + cache->vol = vol; + cache->tail = &cache->chain[HFS_CACHESZ - 1]; + + cache->hits = 0; + cache->misses = 0; + + for (i = 0; i < HFS_CACHESZ; ++i) + { + bucket *b = &cache->chain[i]; + + b->flags = 0; + b->count = 0; + + b->bnum = 0; + b->data = &cache->pool[i]; + + b->cnext = b + 1; + b->cprev = b - 1; + + b->hnext = 0; + b->hprev = 0; + } + + cache->chain[0].cprev = cache->tail; + cache->tail->cnext = &cache->chain[0]; + + for (i = 0; i < HFS_HASHSZ; ++i) + cache->hash[i] = 0; + + return 0; + +fail: + return -1; +} + +# ifdef DEBUG +/* + * NAME: block->showstats() + * DESCRIPTION: output cache hit/miss ratio + */ +void b_showstats(const bcache *cache) +{ + fprintf(stderr, "BLOCK: CACHE vol 0x%lx \"%s\" hit/miss ratio = %.3f\n", + (unsigned long) cache->vol, cache->vol->mdb.drVN, + (float) cache->hits / (float) cache->misses); +} + +/* + * NAME: block->dumpcache() + * DESCRIPTION: dump the cache tables for a volume + */ +void b_dumpcache(const bcache *cache) +{ + const bucket *b; + int i; + + fprintf(stderr, "BLOCK CACHE DUMP:\n"); + + for (i = 0, b = cache->tail->cnext; i < HFS_CACHESZ; ++i, b = b->cnext) + { + if (INUSE(b)) + { + fprintf(stderr, "\t %lu", b->bnum); + if (DIRTY(b)) + fprintf(stderr, "*"); + + fprintf(stderr, ":%u", b->count); + } + } + + fprintf(stderr, "\n"); + + fprintf(stderr, "BLOCK HASH DUMP:\n"); + + for (i = 0; i < HFS_HASHSZ; ++i) + { + int seen = 0; + + for (b = cache->hash[i]; b; b = b->hnext) + { + if (! seen) + fprintf(stderr, " %d:", i); + + if (INUSE(b)) + { + fprintf(stderr, " %lu", b->bnum); + if (DIRTY(b)) + fprintf(stderr, "*"); + + fprintf(stderr, ":%u", b->count); + } + + seen = 1; + } + + if (seen) + fprintf(stderr, "\n"); + } +} +# endif + +/* + * NAME: fillchain() + * DESCRIPTION: fill a chain of bucket buffers with a single read + */ +static +int fillchain(hfsvol *vol, bucket **bptr, unsigned int *count) +{ + bucket *blist[HFS_BLOCKBUFSZ], **start = bptr; + unsigned long bnum; + unsigned int len, i; + + for (len = 0; len < HFS_BLOCKBUFSZ && + (unsigned int) (bptr - start) < *count; ++bptr) + { + if (INUSE(*bptr)) + continue; + + if (len > 0 && (*bptr)->bnum != bnum) + break; + + blist[len++] = *bptr; + bnum = (*bptr)->bnum + 1; + } + + *count = bptr - start; + + if (len == 0) + goto done; + else if (len == 1) + { + if (b_readpb(vol, vol->vstart + blist[0]->bnum, + blist[0]->data, 1) == -1) + goto fail; + } + else + { + block buffer[HFS_BLOCKBUFSZ]; + + if (b_readpb(vol, vol->vstart + blist[0]->bnum, buffer, len) == -1) + goto fail; + + for (i = 0; i < len; ++i) + memcpy(blist[i]->data, buffer[i], HFS_BLOCKSZ); + } + + for (i = 0; i < len; ++i) + { + blist[i]->flags |= HFS_BUCKET_INUSE; + blist[i]->flags &= ~HFS_BUCKET_DIRTY; + } + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: flushchain() + * DESCRIPTION: store a chain of bucket buffers with a single write + */ +static +int flushchain(hfsvol *vol, bucket **bptr, unsigned int *count) +{ + bucket *blist[HFS_BLOCKBUFSZ], **start = bptr; + unsigned long bnum; + unsigned int len, i; + + for (len = 0; len < HFS_BLOCKBUFSZ && + (unsigned int) (bptr - start) < *count; ++bptr) + { + if (! INUSE(*bptr) || ! DIRTY(*bptr)) + continue; + + if (len > 0 && (*bptr)->bnum != bnum) + break; + + blist[len++] = *bptr; + bnum = (*bptr)->bnum + 1; + } + + *count = bptr - start; + + if (len == 0) + goto done; + else if (len == 1) + { + if (b_writepb(vol, vol->vstart + blist[0]->bnum, + blist[0]->data, 1) == -1) + goto fail; + } + else + { + block buffer[HFS_BLOCKBUFSZ]; + + for (i = 0; i < len; ++i) + memcpy(buffer[i], blist[i]->data, HFS_BLOCKSZ); + + if (b_writepb(vol, vol->vstart + blist[0]->bnum, buffer, len) == -1) + goto fail; + } + + for (i = 0; i < len; ++i) + blist[i]->flags &= ~HFS_BUCKET_DIRTY; + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: compare() + * DESCRIPTION: comparison function for qsort of cache bucket pointers + */ +static +int compare(const bucket **b1, const bucket **b2) +{ + long diff; + + diff = (*b1)->bnum - (*b2)->bnum; + + if (diff < 0) + return -1; + else if (diff > 0) + return 1; + else + return 0; +} + +/* + * NAME: dobuckets() + * DESCRIPTION: fill or flush an array of cache buckets to a volume + */ +static +int dobuckets(hfsvol *vol, bucket **chain, unsigned int len, + int (*func)(hfsvol *, bucket **, unsigned int *)) +{ + unsigned int count, i; + int result = 0; + + qsort(chain, len, sizeof(*chain), + (int (*)(const void *, const void *)) compare); + + for (i = 0; i < len; i += count) + { + count = len - i; + if (func(vol, chain + i, &count) == -1) + result = -1; + } + + return result; +} + +# define fillbuckets(vol, chain, len) dobuckets(vol, chain, len, fillchain) +# define flushbuckets(vol, chain, len) dobuckets(vol, chain, len, flushchain) + +/* + * NAME: block->flush() + * DESCRIPTION: commit dirty cache blocks to a volume + */ +int b_flush(hfsvol *vol) +{ + bcache *cache = vol->cache; + bucket *chain[HFS_CACHESZ]; + int i; + + if (cache == 0 || (vol->flags & HFS_VOL_READONLY)) + goto done; + + for (i = 0; i < HFS_CACHESZ; ++i) + chain[i] = &cache->chain[i]; + + if (flushbuckets(vol, chain, HFS_CACHESZ) == -1) + goto fail; + +done: +# ifdef DEBUG + if (cache) + b_showstats(cache); +# endif + + return 0; + +fail: + return -1; +} + +/* + * NAME: block->finish() + * DESCRIPTION: commit and free a volume's block cache + */ +int b_finish(hfsvol *vol) +{ + int result = 0; + + if (vol->cache == 0) + goto done; + +# ifdef DEBUG + b_dumpcache(vol->cache); +# endif + + result = b_flush(vol); + + FREE(vol->cache); + vol->cache = 0; + +done: + return result; +} + +/* + * NAME: findbucket() + * DESCRIPTION: locate a bucket in the cache, and/or its hash slot + */ +static +bucket *findbucket(bcache *cache, unsigned long bnum, bucket ***hslot) +{ + bucket *b; + + *hslot = &cache->hash[bnum & (HFS_HASHSZ - 1)]; + + for (b = **hslot; b; b = b->hnext) + { + if (INUSE(b) && b->bnum == bnum) + break; + } + + return b; +} + +/* + * NAME: reuse() + * DESCRIPTION: free a bucket for reuse, flushing if necessary + */ +static +int reuse(bcache *cache, bucket *b, unsigned long bnum) +{ + bucket *chain[HFS_BLOCKBUFSZ], *bptr; + int i; + +# ifdef DEBUG + if (INUSE(b)) + fprintf(stderr, "BLOCK: CACHE reusing bucket containing " + "vol 0x%lx block %lu:%u\n", + (unsigned long) cache->vol, b->bnum, b->count); +# endif + + if (INUSE(b) && DIRTY(b)) + { + /* flush most recently unused buckets */ + + for (bptr = b, i = 0; i < HFS_BLOCKBUFSZ; ++i) + { + chain[i] = bptr; + bptr = bptr->cprev; + } + + if (flushbuckets(cache->vol, chain, HFS_BLOCKBUFSZ) == -1) + goto fail; + } + + b->flags &= ~HFS_BUCKET_INUSE; + b->count = 1; + b->bnum = bnum; + + return 0; + +fail: + return -1; +} + +/* + * NAME: cplace() + * DESCRIPTION: move a bucket to an appropriate place near head of the chain + */ +static +void cplace(bcache *cache, bucket *b) +{ + bucket *p; + + for (p = cache->tail->cnext; p->count > 1; p = p->cnext) + --p->count; + + b->cnext->cprev = b->cprev; + b->cprev->cnext = b->cnext; + + if (cache->tail == b) + cache->tail = b->cprev; + + b->cprev = p->cprev; + b->cnext = p; + + p->cprev->cnext = b; + p->cprev = b; +} + +/* + * NAME: hplace() + * DESCRIPTION: move a bucket to the head of its hash slot + */ +static +void hplace(bucket **hslot, bucket *b) +{ + if (*hslot != b) + { + if (b->hprev) + *b->hprev = b->hnext; + if (b->hnext) + b->hnext->hprev = b->hprev; + + b->hprev = hslot; + b->hnext = *hslot; + + if (*hslot) + (*hslot)->hprev = &b->hnext; + + *hslot = b; + } +} + +/* + * NAME: getbucket() + * DESCRIPTION: fetch a bucket from the cache, or an empty one to be filled + */ +static +bucket *getbucket(bcache *cache, unsigned long bnum, int fill) +{ + bucket **hslot, *b, *p, *bptr, + *chain[HFS_BLOCKBUFSZ], **slots[HFS_BLOCKBUFSZ]; + + b = findbucket(cache, bnum, &hslot); + + if (b) + { + /* cache hit; move towards head of cache chain */ + + ++cache->hits; + + if (++b->count > b->cprev->count && + b != cache->tail->cnext) + { + p = b->cprev; + + p->cprev->cnext = b; + b->cnext->cprev = p; + + p->cnext = b->cnext; + b->cprev = p->cprev; + + p->cprev = b; + b->cnext = p; + + if (cache->tail == b) + cache->tail = p; + } + } + else + { + /* cache miss; reuse least-used cache bucket */ + + ++cache->misses; + + b = cache->tail; + + if (reuse(cache, b, bnum) == -1) + goto fail; + + if (fill) + { + unsigned int len = 0; + + chain[len] = b; + slots[len++] = hslot; + + for (bptr = b->cprev; + len < (HFS_BLOCKBUFSZ >> 1) && ++bnum < cache->vol->vlen; + bptr = bptr->cprev) + { + if (findbucket(cache, bnum, &hslot)) + break; + + if (reuse(cache, bptr, bnum) == -1) + goto fail; + + chain[len] = bptr; + slots[len++] = hslot; + } + + if (fillbuckets(cache->vol, chain, len) == -1) + goto fail; + + while (--len) + { + cplace(cache, chain[len]); + hplace(slots[len], chain[len]); + } + + hslot = slots[0]; + } + + /* move bucket to appropriate place in chain */ + + cplace(cache, b); + } + + /* insert at front of hash chain */ + + hplace(hslot, b); + + return b; + +fail: + return 0; +} + +/* + * NAME: block->readpb() + * DESCRIPTION: read blocks from the physical medium (bypassing cache) + */ +int b_readpb(hfsvol *vol, unsigned long bnum, block *bp, unsigned int blen) +{ + unsigned long nblocks; + +# ifdef DEBUG + fprintf(stderr, "BLOCK: READ vol 0x%lx block %lu", + (unsigned long) vol, bnum); + if (blen > 1) + fprintf(stderr, "+%u[..%lu]\n", blen - 1, bnum + blen - 1); + else + fprintf(stderr, "\n"); +# endif + + nblocks = os_seek(&vol->priv, vol->base, bnum); + if (nblocks == (unsigned long) -1) + goto fail; + + if (nblocks != bnum) + ERROR(EIO, "block seek failed for read"); + + nblocks = os_read(&vol->priv, bp, blen); + if (nblocks == (unsigned long) -1) + goto fail; + + if (nblocks != blen) + ERROR(EIO, "incomplete block read"); + + return 0; + +fail: + return -1; +} + +/* + * NAME: block->writepb() + * DESCRIPTION: write blocks to the physical medium (bypassing cache) + */ +int b_writepb(hfsvol *vol, unsigned long bnum, const block *bp, + unsigned int blen) +{ + unsigned long nblocks; + +# ifdef DEBUG + fprintf(stderr, "BLOCK: WRITE vol 0x%lx block %lu", + (unsigned long) vol, bnum); + if (blen > 1) + fprintf(stderr, "+%u[..%lu]\n", blen - 1, bnum + blen - 1); + else + fprintf(stderr, "\n"); +# endif + + nblocks = os_seek(&vol->priv, vol->base, bnum); + if (nblocks == (unsigned long) -1) + goto fail; + + if (nblocks != bnum) + ERROR(EIO, "block seek failed for write"); + + nblocks = os_write(&vol->priv, bp, blen); + if (nblocks == (unsigned long) -1) + goto fail; + + if (nblocks != blen) + ERROR(EIO, "incomplete block write"); + + return 0; + +fail: + return -1; +} + +/* + * NAME: block->readlb() + * DESCRIPTION: read a logical block from a volume (or from the cache) + */ +int b_readlb(hfsvol *vol, unsigned long bnum, block *bp) +{ + if (vol->vlen > 0 && bnum >= vol->vlen) + ERROR(EIO, "read nonexistent logical block"); + + if (vol->cache) + { + bucket *b; + + b = getbucket(vol->cache, bnum, 1); + if (b == 0) + goto fail; + + memcpy(bp, b->data, HFS_BLOCKSZ); + } + else + { + if (b_readpb(vol, vol->vstart + bnum, bp, 1) == -1) + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: block->writelb() + * DESCRIPTION: write a logical block to a volume (or to the cache) + */ +int b_writelb(hfsvol *vol, unsigned long bnum, const block *bp) +{ + if (vol->vlen > 0 && bnum >= vol->vlen) + ERROR(EIO, "write nonexistent logical block"); + + if (vol->cache) + { + bucket *b; + + b = getbucket(vol->cache, bnum, 0); + if (b == 0) + goto fail; + + if (! INUSE(b) || + memcmp(b->data, bp, HFS_BLOCKSZ) != 0) + { + memcpy(b->data, bp, HFS_BLOCKSZ); + b->flags |= HFS_BUCKET_INUSE | HFS_BUCKET_DIRTY; + } + } + else + { + if (b_writepb(vol, vol->vstart + bnum, bp, 1) == -1) + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: block->readab() + * DESCRIPTION: read a block from an allocation block from a volume + */ +int b_readab(hfsvol *vol, unsigned int anum, unsigned int index, block *bp) +{ + /* verify the allocation block exists and is marked as in-use */ + + if (anum >= vol->mdb.drNmAlBlks) + ERROR(EIO, "read nonexistent allocation block"); + else if (vol->vbm && ! BMTST(vol->vbm, anum)) + ERROR(EIO, "read unallocated block"); + + return b_readlb(vol, vol->mdb.drAlBlSt + anum * vol->lpa + index, bp); + +fail: + return -1; +} + +/* + * NAME: block->writeab() + * DESCRIPTION: write a block to an allocation block to a volume + */ +int b_writeab(hfsvol *vol, + unsigned int anum, unsigned int index, const block *bp) +{ + /* verify the allocation block exists and is marked as in-use */ + + if (anum >= vol->mdb.drNmAlBlks) + ERROR(EIO, "write nonexistent allocation block"); + else if (vol->vbm && ! BMTST(vol->vbm, anum)) + ERROR(EIO, "write unallocated block"); + + if (v_dirty(vol) == -1) + goto fail; + + return b_writelb(vol, vol->mdb.drAlBlSt + anum * vol->lpa + index, bp); + +fail: + return -1; +} + +/* + * NAME: block->size() + * DESCRIPTION: return the number of physical blocks on a volume's medium + */ +unsigned long b_size(hfsvol *vol) +{ + unsigned long low, high, mid; + block b; + + high = os_seek(&vol->priv, vol->base, -1); + + if (high != (unsigned long) -1 && high > 0) + return high; + + /* manual size detection: first check there is at least 1 block in medium */ + + if (b_readpb(vol, 0, &b, 1) == -1) + ERROR(EIO, "size of medium indeterminable or empty"); + + for (low = 0, high = 2880; + high > 0 && b_readpb(vol, high - 1, &b, 1) != -1; + high <<= 1) + low = high - 1; + + if (high == 0) + ERROR(EIO, "size of medium indeterminable or too large"); + + /* common case: 1440K floppy */ + + if (low == 2879 && b_readpb(vol, 2880, &b, 1) == -1) + return 2880; + + /* binary search for other sizes */ + + while (low < high - 1) + { + mid = (low + high) >> 1; + + if (b_readpb(vol, mid, &b, 1) == -1) + high = mid; + else + low = mid; + } + + return low + 1; + +fail: + return 0; +} diff --git a/libhfs/block.h b/libhfs/block.h new file mode 100644 index 0000000..fdbc099 --- /dev/null +++ b/libhfs/block.h @@ -0,0 +1,40 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: block.h,v 1.10 1998/11/02 22:08:53 rob Exp $ + */ + +int b_init(hfsvol *); +int b_flush(hfsvol *); +int b_finish(hfsvol *); + +int b_readpb(hfsvol *, unsigned long, block *, unsigned int); +int b_writepb(hfsvol *, unsigned long, const block *, unsigned int); + +int b_readlb(hfsvol *, unsigned long, block *); +int b_writelb(hfsvol *, unsigned long, const block *); + +int b_readab(hfsvol *, unsigned int, unsigned int, block *); +int b_writeab(hfsvol *, unsigned int, unsigned int, const block *); + +unsigned long b_size(hfsvol *); + +# ifdef DEBUG +void b_showstats(const bcache *); +void b_dumpcache(const bcache *); +# endif diff --git a/libhfs/btree.c b/libhfs/btree.c new file mode 100644 index 0000000..d7d1916 --- /dev/null +++ b/libhfs/btree.c @@ -0,0 +1,700 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: btree.c,v 1.10 1998/11/02 22:08:54 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include + +# include "libhfs.h" +# include "btree.h" +# include "data.h" +# include "file.h" +# include "block.h" +# include "node.h" + +/* + * NAME: btree->getnode() + * DESCRIPTION: retrieve a numbered node from a B*-tree file + */ +int bt_getnode(node *np, btree *bt, unsigned long nnum) +{ + block *bp = &np->data; + const byte *ptr; + int i; + + np->bt = bt; + np->nnum = nnum; + +# if 0 + fprintf(stderr, "BTREE: GET vol \"%s\" btree \"%s\" node %lu\n", + bt->f.vol->mdb.drVN, bt->f.name, np->nnum); +# endif + + /* verify the node exists and is marked as in-use */ + + if (nnum > 0 && nnum >= bt->hdr.bthNNodes) + ERROR(EIO, "read nonexistent b*-tree node"); + else if (bt->map && ! BMTST(bt->map, nnum)) + ERROR(EIO, "read unallocated b*-tree node"); + + if (f_getblock(&bt->f, nnum, bp) == -1) + goto fail; + + ptr = *bp; + + d_fetchul(&ptr, &np->nd.ndFLink); + d_fetchul(&ptr, &np->nd.ndBLink); + d_fetchsb(&ptr, &np->nd.ndType); + d_fetchsb(&ptr, &np->nd.ndNHeight); + d_fetchuw(&ptr, &np->nd.ndNRecs); + d_fetchsw(&ptr, &np->nd.ndResv2); + + if (np->nd.ndNRecs > HFS_MAX_NRECS) + ERROR(EIO, "too many b*-tree node records"); + + i = np->nd.ndNRecs + 1; + + ptr = *bp + HFS_BLOCKSZ - (2 * i); + + while (i--) + d_fetchuw(&ptr, &np->roff[i]); + + return 0; + +fail: + return -1; +} + +/* + * NAME: btree->putnode() + * DESCRIPTION: store a numbered node into a B*-tree file + */ +int bt_putnode(node *np) +{ + btree *bt = np->bt; + block *bp = &np->data; + byte *ptr; + int i; + +# if 0 + fprintf(stderr, "BTREE: PUT vol \"%s\" btree \"%s\" node %lu\n", + bt->f.vol->mdb.drVN, bt->f.name, np->nnum); +# endif + + /* verify the node exists and is marked as in-use */ + + if (np->nnum > 0 && np->nnum >= bt->hdr.bthNNodes) + ERROR(EIO, "write nonexistent b*-tree node"); + else if (bt->map && ! BMTST(bt->map, np->nnum)) + ERROR(EIO, "write unallocated b*-tree node"); + + ptr = *bp; + + d_storeul(&ptr, np->nd.ndFLink); + d_storeul(&ptr, np->nd.ndBLink); + d_storesb(&ptr, np->nd.ndType); + d_storesb(&ptr, np->nd.ndNHeight); + d_storeuw(&ptr, np->nd.ndNRecs); + d_storesw(&ptr, np->nd.ndResv2); + + if (np->nd.ndNRecs > HFS_MAX_NRECS) + ERROR(EIO, "too many b*-tree node records"); + + i = np->nd.ndNRecs + 1; + + ptr = *bp + HFS_BLOCKSZ - (2 * i); + + while (i--) + d_storeuw(&ptr, np->roff[i]); + + return f_putblock(&bt->f, np->nnum, bp); + +fail: + return -1; +} + +/* + * NAME: btree->readhdr() + * DESCRIPTION: read the header node of a B*-tree + */ +int bt_readhdr(btree *bt) +{ + const byte *ptr; + byte *map = 0; + int i; + unsigned long nnum; + + if (bt_getnode(&bt->hdrnd, bt, 0) == -1) + goto fail; + + if (bt->hdrnd.nd.ndType != ndHdrNode || + bt->hdrnd.nd.ndNRecs != 3 || + bt->hdrnd.roff[0] != 0x00e || + bt->hdrnd.roff[1] != 0x078 || + bt->hdrnd.roff[2] != 0x0f8 || + bt->hdrnd.roff[3] != 0x1f8) + ERROR(EIO, "malformed b*-tree header node"); + + /* read header record */ + + ptr = HFS_NODEREC(bt->hdrnd, 0); + + d_fetchuw(&ptr, &bt->hdr.bthDepth); + d_fetchul(&ptr, &bt->hdr.bthRoot); + d_fetchul(&ptr, &bt->hdr.bthNRecs); + d_fetchul(&ptr, &bt->hdr.bthFNode); + d_fetchul(&ptr, &bt->hdr.bthLNode); + d_fetchuw(&ptr, &bt->hdr.bthNodeSize); + d_fetchuw(&ptr, &bt->hdr.bthKeyLen); + d_fetchul(&ptr, &bt->hdr.bthNNodes); + d_fetchul(&ptr, &bt->hdr.bthFree); + + for (i = 0; i < 76; ++i) + d_fetchsb(&ptr, &bt->hdr.bthResv[i]); + + if (bt->hdr.bthNodeSize != HFS_BLOCKSZ) + ERROR(EINVAL, "unsupported b*-tree node size"); + + /* read map record; construct btree bitmap */ + /* don't set bt->map until we're done, since getnode() checks it */ + + map = ALLOC(byte, HFS_MAP1SZ); + if (map == 0) + ERROR(ENOMEM, 0); + + memcpy(map, HFS_NODEREC(bt->hdrnd, 2), HFS_MAP1SZ); + bt->mapsz = HFS_MAP1SZ; + + /* read continuation map records, if any */ + + nnum = bt->hdrnd.nd.ndFLink; + + while (nnum) + { + node n; + byte *newmap; + + if (bt_getnode(&n, bt, nnum) == -1) + goto fail; + + if (n.nd.ndType != ndMapNode || + n.nd.ndNRecs != 1 || + n.roff[0] != 0x00e || + n.roff[1] != 0x1fa) + ERROR(EIO, "malformed b*-tree map node"); + + newmap = REALLOC(map, byte, bt->mapsz + HFS_MAPXSZ); + if (newmap == 0) + ERROR(ENOMEM, 0); + + map = newmap; + + memcpy(map + bt->mapsz, HFS_NODEREC(n, 0), HFS_MAPXSZ); + bt->mapsz += HFS_MAPXSZ; + + nnum = n.nd.ndFLink; + } + + bt->map = map; + + return 0; + +fail: + FREE(map); + return -1; +} + +/* + * NAME: btree->writehdr() + * DESCRIPTION: write the header node of a B*-tree + */ +int bt_writehdr(btree *bt) +{ + byte *ptr, *map; + unsigned long mapsz, nnum; + int i; + + ASSERT(bt->hdrnd.bt == bt && + bt->hdrnd.nnum == 0 && + bt->hdrnd.nd.ndType == ndHdrNode && + bt->hdrnd.nd.ndNRecs == 3); + + ptr = HFS_NODEREC(bt->hdrnd, 0); + + d_storeuw(&ptr, bt->hdr.bthDepth); + d_storeul(&ptr, bt->hdr.bthRoot); + d_storeul(&ptr, bt->hdr.bthNRecs); + d_storeul(&ptr, bt->hdr.bthFNode); + d_storeul(&ptr, bt->hdr.bthLNode); + d_storeuw(&ptr, bt->hdr.bthNodeSize); + d_storeuw(&ptr, bt->hdr.bthKeyLen); + d_storeul(&ptr, bt->hdr.bthNNodes); + d_storeul(&ptr, bt->hdr.bthFree); + + for (i = 0; i < 76; ++i) + d_storesb(&ptr, bt->hdr.bthResv[i]); + + memcpy(HFS_NODEREC(bt->hdrnd, 2), bt->map, HFS_MAP1SZ); + + if (bt_putnode(&bt->hdrnd) == -1) + goto fail; + + map = bt->map + HFS_MAP1SZ; + mapsz = bt->mapsz - HFS_MAP1SZ; + + nnum = bt->hdrnd.nd.ndFLink; + + while (mapsz) + { + node n; + + if (nnum == 0) + ERROR(EIO, "truncated b*-tree map"); + + if (bt_getnode(&n, bt, nnum) == -1) + goto fail; + + if (n.nd.ndType != ndMapNode || + n.nd.ndNRecs != 1 || + n.roff[0] != 0x00e || + n.roff[1] != 0x1fa) + ERROR(EIO, "malformed b*-tree map node"); + + memcpy(HFS_NODEREC(n, 0), map, HFS_MAPXSZ); + + if (bt_putnode(&n) == -1) + goto fail; + + map += HFS_MAPXSZ; + mapsz -= HFS_MAPXSZ; + + nnum = n.nd.ndFLink; + } + + bt->flags &= ~HFS_BT_UPDATE_HDR; + + return 0; + +fail: + return -1; +} + +/* High-Level B*-Tree Routines ============================================= */ + +/* + * NAME: btree->space() + * DESCRIPTION: assert space for new records, or extend the file + */ +int bt_space(btree *bt, unsigned int nrecs) +{ + unsigned int nnodes; + long space; + + nnodes = nrecs * (bt->hdr.bthDepth + 1); + + if (nnodes <= bt->hdr.bthFree) + goto done; + + /* make sure the extents tree has room too */ + + if (bt != &bt->f.vol->ext) + { + if (bt_space(&bt->f.vol->ext, 1) == -1) + goto fail; + } + + space = f_alloc(&bt->f); + if (space == -1) + goto fail; + + nnodes = space * (bt->f.vol->mdb.drAlBlkSiz / bt->hdr.bthNodeSize); + + bt->hdr.bthNNodes += nnodes; + bt->hdr.bthFree += nnodes; + + bt->flags |= HFS_BT_UPDATE_HDR; + + bt->f.vol->flags |= HFS_VOL_UPDATE_ALTMDB; + + while (bt->hdr.bthNNodes > bt->mapsz * 8) + { + byte *newmap; + node mapnd; + + /* extend tree map */ + + newmap = REALLOC(bt->map, byte, bt->mapsz + HFS_MAPXSZ); + if (newmap == 0) + ERROR(ENOMEM, 0); + + memset(newmap + bt->mapsz, 0, HFS_MAPXSZ); + + bt->map = newmap; + bt->mapsz += HFS_MAPXSZ; + + n_init(&mapnd, bt, ndMapNode, 0); + if (n_new(&mapnd) == -1) + goto fail; + + mapnd.nd.ndNRecs = 1; + mapnd.roff[1] = 0x1fa; + + /* link the new map node */ + + if (bt->hdrnd.nd.ndFLink == 0) + { + bt->hdrnd.nd.ndFLink = mapnd.nnum; + mapnd.nd.ndBLink = 0; + } + else + { + node n; + unsigned long nnum; + + nnum = bt->hdrnd.nd.ndFLink; + + while (1) + { + if (bt_getnode(&n, bt, nnum) == -1) + goto fail; + + if (n.nd.ndFLink == 0) + break; + + nnum = n.nd.ndFLink; + } + + n.nd.ndFLink = mapnd.nnum; + mapnd.nd.ndBLink = n.nnum; + + if (bt_putnode(&n) == -1) + goto fail; + } + + if (bt_putnode(&mapnd) == -1) + goto fail; + } + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: insertx() + * DESCRIPTION: recursively locate a node and insert a record + */ +static +int insertx(node *np, byte *record, int *reclen) +{ + node child; + byte *rec; + int result = 0; + + if (n_search(np, record)) + ERROR(EIO, "b*-tree record already exists"); + + switch (np->nd.ndType) + { + case ndIndxNode: + if (np->rnum == -1) + rec = HFS_NODEREC(*np, 0); + else + rec = HFS_NODEREC(*np, np->rnum); + + if (bt_getnode(&child, np->bt, d_getul(HFS_RECDATA(rec))) == -1 || + insertx(&child, record, reclen) == -1) + goto fail; + + if (np->rnum == -1) + { + n_index(&child, rec, 0); + if (*reclen == 0) + { + result = bt_putnode(np); + goto done; + } + } + + if (*reclen) + result = n_insert(np, record, reclen); + + break; + + case ndLeafNode: + result = n_insert(np, record, reclen); + break; + + default: + ERROR(EIO, "unexpected b*-tree node"); + } + +done: + return result; + +fail: + return -1; +} + +/* + * NAME: btree->insert() + * DESCRIPTION: insert a new node record into a tree + */ +int bt_insert(btree *bt, const byte *record, unsigned int reclen) +{ + node root; + byte newrec[HFS_MAX_RECLEN]; + + if (bt->hdr.bthRoot == 0) + { + /* create root node */ + + n_init(&root, bt, ndLeafNode, 1); + if (n_new(&root) == -1 || + bt_putnode(&root) == -1) + goto fail; + + bt->hdr.bthDepth = 1; + bt->hdr.bthRoot = root.nnum; + bt->hdr.bthFNode = root.nnum; + bt->hdr.bthLNode = root.nnum; + + bt->flags |= HFS_BT_UPDATE_HDR; + } + else if (bt_getnode(&root, bt, bt->hdr.bthRoot) == -1) + goto fail; + + memcpy(newrec, record, reclen); + + if (insertx(&root, newrec, &reclen) == -1) + goto fail; + + if (reclen) + { + byte oroot[HFS_MAX_RECLEN]; + unsigned int orootlen; + + /* root node was split; create a new root */ + + n_index(&root, oroot, &orootlen); + + n_init(&root, bt, ndIndxNode, root.nd.ndNHeight + 1); + if (n_new(&root) == -1) + goto fail; + + ++bt->hdr.bthDepth; + bt->hdr.bthRoot = root.nnum; + + bt->flags |= HFS_BT_UPDATE_HDR; + + /* insert index records for new root */ + + n_search(&root, oroot); + n_insertx(&root, oroot, orootlen); + + n_search(&root, newrec); + n_insertx(&root, newrec, reclen); + + if (bt_putnode(&root) == -1) + goto fail; + } + + ++bt->hdr.bthNRecs; + bt->flags |= HFS_BT_UPDATE_HDR; + + return 0; + +fail: + return -1; +} + +/* + * NAME: deletex() + * DESCRIPTION: recursively locate a node and delete a record + */ +static +int deletex(node *np, const byte *key, byte *record, int *flag) +{ + node child; + byte *rec; + int found, result = 0; + + found = n_search(np, key); + + switch (np->nd.ndType) + { + case ndIndxNode: + if (np->rnum == -1) + ERROR(EIO, "b*-tree record not found"); + + rec = HFS_NODEREC(*np, np->rnum); + + if (bt_getnode(&child, np->bt, d_getul(HFS_RECDATA(rec))) == -1 || + deletex(&child, key, rec, flag) == -1) + goto fail; + + if (*flag) + { + *flag = 0; + + if (HFS_RECKEYLEN(rec) == 0) + { + result = n_delete(np, record, flag); + break; + } + + if (np->rnum == 0) + { + /* propagate index record change into parent */ + + n_index(np, record, 0); + *flag = 1; + } + + result = bt_putnode(np); + } + + break; + + case ndLeafNode: + if (found == 0) + ERROR(EIO, "b*-tree record not found"); + + result = n_delete(np, record, flag); + break; + + default: + ERROR(EIO, "unexpected b*-tree node"); + } + + return result; + +fail: + return -1; +} + +/* + * NAME: btree->delete() + * DESCRIPTION: remove a node record from a tree + */ +int bt_delete(btree *bt, const byte *key) +{ + node root; + byte record[HFS_MAX_RECLEN]; + int flag = 0; + + if (bt->hdr.bthRoot == 0) + ERROR(EIO, "empty b*-tree"); + + if (bt_getnode(&root, bt, bt->hdr.bthRoot) == -1 || + deletex(&root, key, record, &flag) == -1) + goto fail; + + if (bt->hdr.bthDepth > 1 && root.nd.ndNRecs == 1) + { + const byte *rec; + + /* root only has one record; eliminate it and decrease the tree depth */ + + rec = HFS_NODEREC(root, 0); + + --bt->hdr.bthDepth; + bt->hdr.bthRoot = d_getul(HFS_RECDATA(rec)); + + if (n_free(&root) == -1) + goto fail; + } + else if (bt->hdr.bthDepth == 1 && root.nd.ndNRecs == 0) + { + /* root node was deleted */ + + bt->hdr.bthDepth = 0; + bt->hdr.bthRoot = 0; + } + + --bt->hdr.bthNRecs; + bt->flags |= HFS_BT_UPDATE_HDR; + + return 0; + +fail: + return -1; +} + +/* + * NAME: btree->search() + * DESCRIPTION: locate a data record given a search key + */ +int bt_search(btree *bt, const byte *key, node *np) +{ + int found = 0; + unsigned long nnum; + + nnum = bt->hdr.bthRoot; + + if (nnum == 0) + ERROR(ENOENT, 0); + + while (1) + { + const byte *rec; + + if (bt_getnode(np, bt, nnum) == -1) + { + found = -1; + goto fail; + } + + found = n_search(np, key); + + switch (np->nd.ndType) + { + case ndIndxNode: + if (np->rnum == -1) + ERROR(ENOENT, 0); + + rec = HFS_NODEREC(*np, np->rnum); + nnum = d_getul(HFS_RECDATA(rec)); + + break; + + case ndLeafNode: + if (! found) + ERROR(ENOENT, 0); + + goto done; + + default: + found = -1; + ERROR(EIO, "unexpected b*-tree node"); + } + } + +done: +fail: + return found; +} diff --git a/libhfs/btree.h b/libhfs/btree.h new file mode 100644 index 0000000..b3775a1 --- /dev/null +++ b/libhfs/btree.h @@ -0,0 +1,33 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: btree.h,v 1.8 1998/11/02 22:08:55 rob Exp $ + */ + +int bt_getnode(node *, btree *, unsigned long); +int bt_putnode(node *); + +int bt_readhdr(btree *); +int bt_writehdr(btree *); + +int bt_space(btree *, unsigned int); + +int bt_insert(btree *, const byte *, unsigned int); +int bt_delete(btree *, const byte *); + +int bt_search(btree *, const byte *, node *); diff --git a/libhfs/config.h b/libhfs/config.h new file mode 100644 index 0000000..ef118a9 --- /dev/null +++ b/libhfs/config.h @@ -0,0 +1,58 @@ +/* config.h. Generated automatically by configure. */ +/* config.h.in. Generated automatically from configure.in by autoheader. */ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: acconfig.h,v 1.5 1998/04/11 08:27:11 rob Exp $ + */ + +/***************************************************************************** + * Definitions selected automatically by `configure' * + *****************************************************************************/ + +/* Define to empty if the keyword does not work. */ +/* #undef const */ + +/* Define to `unsigned' if doesn't define. */ +/* #undef size_t */ + +/* Define if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if your declares struct tm. */ +/* #undef TM_IN_SYS_TIME */ + +/* Define if you want to enable diagnostic debugging support. */ +/* #undef DEBUG */ + +/* Define if you have the mktime function. */ +#define HAVE_MKTIME 1 + +/* Define if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/***************************************************************************** + * End of automatically configured definitions * + *****************************************************************************/ + +# ifdef DEBUG +# include +# endif diff --git a/libhfs/data.c b/libhfs/data.c new file mode 100644 index 0000000..0dee6bc --- /dev/null +++ b/libhfs/data.c @@ -0,0 +1,485 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: data.c,v 1.7 1998/11/02 22:08:57 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include + +# ifdef TM_IN_SYS_TIME +# include +# endif + +# include "data.h" + +# define TIMEDIFF 2082844800UL + +static +time_t tzdiff = -1; + +const +unsigned char hfs_charorder[256] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + + 0x20, 0x22, 0x23, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + + 0x47, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x67, 0x69, + 0x6b, 0x6d, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7f, + 0x8d, 0x8f, 0x91, 0x93, 0x96, 0x98, 0x9f, 0xa1, + 0xa3, 0xa5, 0xa8, 0xaa, 0xab, 0xac, 0xad, 0xae, + + 0x54, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x67, 0x69, + 0x6b, 0x6d, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7f, + 0x8d, 0x8f, 0x91, 0x93, 0x96, 0x98, 0x9f, 0xa1, + 0xa3, 0xa5, 0xa8, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + + 0x4c, 0x50, 0x5c, 0x62, 0x7d, 0x81, 0x9a, 0x55, + 0x4a, 0x56, 0x4c, 0x4e, 0x50, 0x5c, 0x62, 0x64, + 0x65, 0x66, 0x6f, 0x70, 0x71, 0x72, 0x7d, 0x89, + 0x8a, 0x8b, 0x81, 0x83, 0x9c, 0x9d, 0x9e, 0x9a, + + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0x95, + 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0x52, 0x85, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0x57, 0x8c, 0xcc, 0x52, 0x85, + + 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0x26, + 0x27, 0xd4, 0x20, 0x4a, 0x4e, 0x83, 0x87, 0x87, + 0xd5, 0xd6, 0x24, 0x25, 0x2d, 0x2e, 0xd7, 0xd8, + 0xa7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +/* + * NAME: data->getsb() + * DESCRIPTION: marshal 1 signed byte into local host format + */ +signed char d_getsb(register const unsigned char *ptr) +{ + return ptr[0]; +} + +/* + * NAME: data->getub() + * DESCRIPTION: marshal 1 unsigned byte into local host format + */ +unsigned char d_getub(register const unsigned char *ptr) +{ + return ptr[0]; +} + +/* + * NAME: data->getsw() + * DESCRIPTION: marshal 2 signed bytes into local host format + */ +signed short d_getsw(register const unsigned char *ptr) +{ + return + ((( signed short) ptr[0] << 8) | + ((unsigned short) ptr[1] << 0)); +} + +/* + * NAME: data->getuw() + * DESCRIPTION: marshal 2 unsigned bytes into local host format + */ +unsigned short d_getuw(register const unsigned char *ptr) +{ + return + (((unsigned short) ptr[0] << 8) | + ((unsigned short) ptr[1] << 0)); +} + +/* + * NAME: data->getsl() + * DESCRIPTION: marshal 4 signed bytes into local host format + */ +signed long d_getsl(register const unsigned char *ptr) +{ + return + ((( signed long) ptr[0] << 24) | + ((unsigned long) ptr[1] << 16) | + ((unsigned long) ptr[2] << 8) | + ((unsigned long) ptr[3] << 0)); +} + +/* + * NAME: data->getul() + * DESCRIPTION: marshal 4 unsigned bytes into local host format + */ +unsigned long d_getul(register const unsigned char *ptr) +{ + return + (((unsigned long) ptr[0] << 24) | + ((unsigned long) ptr[1] << 16) | + ((unsigned long) ptr[2] << 8) | + ((unsigned long) ptr[3] << 0)); +} + +/* + * NAME: data->putsb() + * DESCRIPTION: marshal 1 signed byte out in big-endian format + */ +void d_putsb(register unsigned char *ptr, + register signed char data) +{ + *ptr = data; +} + +/* + * NAME: data->putub() + * DESCRIPTION: marshal 1 unsigned byte out in big-endian format + */ +void d_putub(register unsigned char *ptr, + register unsigned char data) +{ + *ptr = data; +} + +/* + * NAME: data->putsw() + * DESCRIPTION: marshal 2 signed bytes out in big-endian format + */ +void d_putsw(register unsigned char *ptr, + register signed short data) +{ + *ptr++ = ((unsigned short) data & 0xff00) >> 8; + *ptr = ((unsigned short) data & 0x00ff) >> 0; +} + +/* + * NAME: data->putuw() + * DESCRIPTION: marshal 2 unsigned bytes out in big-endian format + */ +void d_putuw(register unsigned char *ptr, + register unsigned short data) +{ + *ptr++ = (data & 0xff00) >> 8; + *ptr = (data & 0x00ff) >> 0; +} + +/* + * NAME: data->putsl() + * DESCRIPTION: marshal 4 signed bytes out in big-endian format + */ +void d_putsl(register unsigned char *ptr, + register signed long data) +{ + *ptr++ = ((unsigned long) data & 0xff000000UL) >> 24; + *ptr++ = ((unsigned long) data & 0x00ff0000UL) >> 16; + *ptr++ = ((unsigned long) data & 0x0000ff00UL) >> 8; + *ptr = ((unsigned long) data & 0x000000ffUL) >> 0; +} + +/* + * NAME: data->putul() + * DESCRIPTION: marshal 4 unsigned bytes out in big-endian format + */ +void d_putul(register unsigned char *ptr, + register unsigned long data) +{ + *ptr++ = (data & 0xff000000UL) >> 24; + *ptr++ = (data & 0x00ff0000UL) >> 16; + *ptr++ = (data & 0x0000ff00UL) >> 8; + *ptr = (data & 0x000000ffUL) >> 0; +} + +/* + * NAME: data->fetchsb() + * DESCRIPTION: incrementally retrieve a signed byte of data + */ +void d_fetchsb(register const unsigned char **ptr, + register signed char *dest) +{ + *dest = *(*ptr)++; +} + +/* + * NAME: data->fetchub() + * DESCRIPTION: incrementally retrieve an unsigned byte of data + */ +void d_fetchub(register const unsigned char **ptr, + register unsigned char *dest) +{ + *dest = *(*ptr)++; +} + +/* + * NAME: data->fetchsw() + * DESCRIPTION: incrementally retrieve a signed word of data + */ +void d_fetchsw(register const unsigned char **ptr, + register signed short *dest) +{ + *dest = + ((( signed short) (*ptr)[0] << 8) | + ((unsigned short) (*ptr)[1] << 0)); + *ptr += 2; +} + +/* + * NAME: data->fetchuw() + * DESCRIPTION: incrementally retrieve an unsigned word of data + */ +void d_fetchuw(register const unsigned char **ptr, + register unsigned short *dest) +{ + *dest = + (((unsigned short) (*ptr)[0] << 8) | + ((unsigned short) (*ptr)[1] << 0)); + *ptr += 2; +} + +/* + * NAME: data->fetchsl() + * DESCRIPTION: incrementally retrieve a signed long word of data + */ +void d_fetchsl(register const unsigned char **ptr, + register signed long *dest) +{ + *dest = + ((( signed long) (*ptr)[0] << 24) | + ((unsigned long) (*ptr)[1] << 16) | + ((unsigned long) (*ptr)[2] << 8) | + ((unsigned long) (*ptr)[3] << 0)); + *ptr += 4; +} + +/* + * NAME: data->fetchul() + * DESCRIPTION: incrementally retrieve an unsigned long word of data + */ +void d_fetchul(register const unsigned char **ptr, + register unsigned long *dest) +{ + *dest = + (((unsigned long) (*ptr)[0] << 24) | + ((unsigned long) (*ptr)[1] << 16) | + ((unsigned long) (*ptr)[2] << 8) | + ((unsigned long) (*ptr)[3] << 0)); + *ptr += 4; +} + +/* + * NAME: data->storesb() + * DESCRIPTION: incrementally store a signed byte of data + */ +void d_storesb(register unsigned char **ptr, + register signed char data) +{ + *(*ptr)++ = data; +} + +/* + * NAME: data->storeub() + * DESCRIPTION: incrementally store an unsigned byte of data + */ +void d_storeub(register unsigned char **ptr, + register unsigned char data) +{ + *(*ptr)++ = data; +} + +/* + * NAME: data->storesw() + * DESCRIPTION: incrementally store a signed word of data + */ +void d_storesw(register unsigned char **ptr, + register signed short data) +{ + *(*ptr)++ = ((unsigned short) data & 0xff00) >> 8; + *(*ptr)++ = ((unsigned short) data & 0x00ff) >> 0; +} + +/* + * NAME: data->storeuw() + * DESCRIPTION: incrementally store an unsigned word of data + */ +void d_storeuw(register unsigned char **ptr, + register unsigned short data) +{ + *(*ptr)++ = (data & 0xff00) >> 8; + *(*ptr)++ = (data & 0x00ff) >> 0; +} + +/* + * NAME: data->storesl() + * DESCRIPTION: incrementally store a signed long word of data + */ +void d_storesl(register unsigned char **ptr, + register signed long data) +{ + *(*ptr)++ = ((unsigned long) data & 0xff000000UL) >> 24; + *(*ptr)++ = ((unsigned long) data & 0x00ff0000UL) >> 16; + *(*ptr)++ = ((unsigned long) data & 0x0000ff00UL) >> 8; + *(*ptr)++ = ((unsigned long) data & 0x000000ffUL) >> 0; +} + +/* + * NAME: data->storeul() + * DESCRIPTION: incrementally store an unsigned long word of data + */ +void d_storeul(register unsigned char **ptr, + register unsigned long data) +{ + *(*ptr)++ = (data & 0xff000000UL) >> 24; + *(*ptr)++ = (data & 0x00ff0000UL) >> 16; + *(*ptr)++ = (data & 0x0000ff00UL) >> 8; + *(*ptr)++ = (data & 0x000000ffUL) >> 0; +} + +/* + * NAME: data->fetchstr() + * DESCRIPTION: incrementally retrieve a string + */ +void d_fetchstr(const unsigned char **ptr, char *dest, unsigned size) +{ + unsigned len; + + len = d_getub(*ptr); + + if (len > 0 && len < size) + memcpy(dest, *ptr + 1, len); + else + len = 0; + + dest[len] = 0; + + *ptr += size; +} + +/* + * NAME: data->storestr() + * DESCRIPTION: incrementally store a string + */ +void d_storestr(unsigned char **ptr, const char *src, unsigned size) +{ + unsigned len; + + len = strlen(src); + if (len > --size) + len = 0; + + d_storeub(ptr, len); + + memcpy(*ptr, src, len); + memset(*ptr + len, 0, size - len); + + *ptr += size; +} + +/* + * NAME: data->relstring() + * DESCRIPTION: compare two strings as per MacOS for HFS + */ +int d_relstring(const char *str1, const char *str2) +{ + register int diff; + + while (*str1 && *str2) + { + diff = hfs_charorder[(unsigned char) *str1] - + hfs_charorder[(unsigned char) *str2]; + + if (diff) + return diff; + + ++str1, ++str2; + } + + if (! *str1 && *str2) + return -1; + else if (*str1 && ! *str2) + return 1; + + return 0; +} + +/* + * NAME: calctzdiff() + * DESCRIPTION: calculate the timezone difference between local time and UTC + */ +static +void calctzdiff(void) +{ +# ifdef HAVE_MKTIME + + time_t t; + int isdst; + struct tm tm; + const struct tm *tmp; + + time(&t); + isdst = localtime(&t)->tm_isdst; + + tmp = gmtime(&t); + if (tmp) + { + tm = *tmp; + tm.tm_isdst = isdst; + + tzdiff = t - mktime(&tm); + } + else + tzdiff = 0; + +# else + + tzdiff = 0; + +# endif +} + +/* + * NAME: data->ltime() + * DESCRIPTION: convert MacOS time to local time + */ +time_t d_ltime(unsigned long mtime) +{ + if (tzdiff == -1) + calctzdiff(); + + return (time_t) (mtime - TIMEDIFF) - tzdiff; +} + +/* + * NAME: data->mtime() + * DESCRIPTION: convert local time to MacOS time + */ +unsigned long d_mtime(time_t ltime) +{ + if (tzdiff == -1) + calctzdiff(); + + return (unsigned long) (ltime + tzdiff) + TIMEDIFF; +} diff --git a/libhfs/data.h b/libhfs/data.h new file mode 100644 index 0000000..fd38e75 --- /dev/null +++ b/libhfs/data.h @@ -0,0 +1,58 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: data.h,v 1.7 1998/11/02 22:08:58 rob Exp $ + */ + +extern const unsigned char hfs_charorder[]; + + signed char d_getsb(register const unsigned char *); +unsigned char d_getub(register const unsigned char *); + signed short d_getsw(register const unsigned char *); +unsigned short d_getuw(register const unsigned char *); + signed long d_getsl(register const unsigned char *); +unsigned long d_getul(register const unsigned char *); + +void d_putsb(register unsigned char *, register signed char); +void d_putub(register unsigned char *, register unsigned char); +void d_putsw(register unsigned char *, register signed short); +void d_putuw(register unsigned char *, register unsigned short); +void d_putsl(register unsigned char *, register signed long); +void d_putul(register unsigned char *, register unsigned long); + +void d_fetchsb(register const unsigned char **, register signed char *); +void d_fetchub(register const unsigned char **, register unsigned char *); +void d_fetchsw(register const unsigned char **, register signed short *); +void d_fetchuw(register const unsigned char **, register unsigned short *); +void d_fetchsl(register const unsigned char **, register signed long *); +void d_fetchul(register const unsigned char **, register unsigned long *); + +void d_storesb(register unsigned char **, register signed char); +void d_storeub(register unsigned char **, register unsigned char); +void d_storesw(register unsigned char **, register signed short); +void d_storeuw(register unsigned char **, register unsigned short); +void d_storesl(register unsigned char **, register signed long); +void d_storeul(register unsigned char **, register unsigned long); + +void d_fetchstr(const unsigned char **, char *, unsigned); +void d_storestr(unsigned char **, const char *, unsigned); + +int d_relstring(const char *, const char *); + +time_t d_ltime(unsigned long); +unsigned long d_mtime(time_t); diff --git a/libhfs/file.c b/libhfs/file.c new file mode 100644 index 0000000..3fcc435 --- /dev/null +++ b/libhfs/file.c @@ -0,0 +1,520 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: file.c,v 1.9 1998/11/02 22:08:59 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include + +# include "libhfs.h" +# include "file.h" +# include "btree.h" +# include "record.h" +# include "volume.h" + +/* + * NAME: file->init() + * DESCRIPTION: initialize file structure + */ +void f_init(hfsfile *file, hfsvol *vol, long cnid, const char *name) +{ + int i; + + file->vol = vol; + file->parid = 0; + + strcpy(file->name, name); + + file->cat.cdrType = cdrFilRec; + file->cat.cdrResrv2 = 0; + + file->cat.u.fil.filFlags = 0; + file->cat.u.fil.filTyp = 0; + + file->cat.u.fil.filUsrWds.fdType = 0; + file->cat.u.fil.filUsrWds.fdCreator = 0; + file->cat.u.fil.filUsrWds.fdFlags = 0; + file->cat.u.fil.filUsrWds.fdLocation.v = 0; + file->cat.u.fil.filUsrWds.fdLocation.h = 0; + file->cat.u.fil.filUsrWds.fdFldr = 0; + + file->cat.u.fil.filFlNum = cnid; + file->cat.u.fil.filStBlk = 0; + file->cat.u.fil.filLgLen = 0; + file->cat.u.fil.filPyLen = 0; + file->cat.u.fil.filRStBlk = 0; + file->cat.u.fil.filRLgLen = 0; + file->cat.u.fil.filRPyLen = 0; + file->cat.u.fil.filCrDat = 0; + file->cat.u.fil.filMdDat = 0; + file->cat.u.fil.filBkDat = 0; + + file->cat.u.fil.filFndrInfo.fdIconID = 0; + for (i = 0; i < 4; ++i) + file->cat.u.fil.filFndrInfo.fdUnused[i] = 0; + file->cat.u.fil.filFndrInfo.fdComment = 0; + file->cat.u.fil.filFndrInfo.fdPutAway = 0; + + file->cat.u.fil.filClpSize = 0; + + for (i = 0; i < 3; ++i) + { + file->cat.u.fil.filExtRec[i].xdrStABN = 0; + file->cat.u.fil.filExtRec[i].xdrNumABlks = 0; + + file->cat.u.fil.filRExtRec[i].xdrStABN = 0; + file->cat.u.fil.filRExtRec[i].xdrNumABlks = 0; + } + + file->cat.u.fil.filResrv = 0; + + f_selectfork(file, fkData); + + file->flags = 0; + + file->prev = 0; + file->next = 0; +} + +/* + * NAME: file->selectfork() + * DESCRIPTION: choose a fork for file operations + */ +void f_selectfork(hfsfile *file, int fork) +{ + file->fork = fork; + + memcpy(&file->ext, fork == fkData ? + &file->cat.u.fil.filExtRec : &file->cat.u.fil.filRExtRec, + sizeof(ExtDataRec)); + + file->fabn = 0; + file->pos = 0; +} + +/* + * NAME: file->getptrs() + * DESCRIPTION: make pointers to the current fork's lengths and extents + */ +void f_getptrs(hfsfile *file, ExtDataRec **extrec, + unsigned long **lglen, unsigned long **pylen) +{ + if (file->fork == fkData) + { + if (extrec) + *extrec = &file->cat.u.fil.filExtRec; + if (lglen) + *lglen = &file->cat.u.fil.filLgLen; + if (pylen) + *pylen = &file->cat.u.fil.filPyLen; + } + else + { + if (extrec) + *extrec = &file->cat.u.fil.filRExtRec; + if (lglen) + *lglen = &file->cat.u.fil.filRLgLen; + if (pylen) + *pylen = &file->cat.u.fil.filRPyLen; + } +} + +/* + * NAME: file->doblock() + * DESCRIPTION: read or write a numbered block from a file + */ +int f_doblock(hfsfile *file, unsigned long num, block *bp, + int (*func)(hfsvol *, unsigned int, unsigned int, block *)) +{ + unsigned int abnum; + unsigned int blnum; + unsigned int fabn; + int i; + + abnum = num / file->vol->lpa; + blnum = num % file->vol->lpa; + + /* locate the appropriate extent record */ + + fabn = file->fabn; + + if (abnum < fabn) + { + ExtDataRec *extrec; + + f_getptrs(file, &extrec, 0, 0); + + fabn = file->fabn = 0; + memcpy(&file->ext, extrec, sizeof(ExtDataRec)); + } + else + abnum -= fabn; + + while (1) + { + unsigned int n; + + for (i = 0; i < 3; ++i) + { + n = file->ext[i].xdrNumABlks; + + if (abnum < n) + return func(file->vol, file->ext[i].xdrStABN + abnum, blnum, bp); + + fabn += n; + abnum -= n; + } + + if (v_extsearch(file, fabn, &file->ext, 0) <= 0) + goto fail; + + file->fabn = fabn; + } + +fail: + return -1; +} + +/* + * NAME: file->addextent() + * DESCRIPTION: add an extent to a file + */ +int f_addextent(hfsfile *file, ExtDescriptor *blocks) +{ + hfsvol *vol = file->vol; + ExtDataRec *extrec; + unsigned long *pylen; + unsigned int start, end; + node n; + int i; + + f_getptrs(file, &extrec, 0, &pylen); + + start = file->fabn; + end = *pylen / vol->mdb.drAlBlkSiz; + + n.nnum = 0; + i = -1; + + while (start < end) + { + for (i = 0; i < 3; ++i) + { + unsigned int num; + + num = file->ext[i].xdrNumABlks; + start += num; + + if (start == end) + break; + else if (start > end) + ERROR(EIO, "file extents exceed file physical length"); + else if (num == 0) + ERROR(EIO, "empty file extent"); + } + + if (start == end) + break; + + if (v_extsearch(file, start, &file->ext, &n) <= 0) + goto fail; + + file->fabn = start; + } + + if (i >= 0 && + file->ext[i].xdrStABN + file->ext[i].xdrNumABlks == blocks->xdrStABN) + file->ext[i].xdrNumABlks += blocks->xdrNumABlks; + else + { + /* create a new extent descriptor */ + + if (++i < 3) + file->ext[i] = *blocks; + else + { + ExtKeyRec key; + byte record[HFS_MAX_EXTRECLEN]; + unsigned int reclen; + + /* record is full; create a new one */ + + file->ext[0] = *blocks; + + for (i = 1; i < 3; ++i) + { + file->ext[i].xdrStABN = 0; + file->ext[i].xdrNumABlks = 0; + } + + file->fabn = start; + + r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, end); + r_packextrec(&key, &file->ext, record, &reclen); + + if (bt_insert(&vol->ext, record, reclen) == -1) + goto fail; + + i = -1; + } + } + + if (i >= 0) + { + /* store the modified extent record */ + + if (file->fabn) + { + if ((n.nnum == 0 && + v_extsearch(file, file->fabn, 0, &n) <= 0) || + v_putextrec(&file->ext, &n) == -1) + goto fail; + } + else + memcpy(extrec, &file->ext, sizeof(ExtDataRec)); + } + + *pylen += blocks->xdrNumABlks * vol->mdb.drAlBlkSiz; + + file->flags |= HFS_FILE_UPDATE_CATREC; + + return 0; + +fail: + return -1; +} + +/* + * NAME: file->alloc() + * DESCRIPTION: reserve allocation blocks for a file + */ +long f_alloc(hfsfile *file) +{ + hfsvol *vol = file->vol; + unsigned long clumpsz; + ExtDescriptor blocks; + + clumpsz = file->cat.u.fil.filClpSize; + if (clumpsz == 0) + { + if (file == &vol->ext.f) + clumpsz = vol->mdb.drXTClpSiz; + else if (file == &vol->cat.f) + clumpsz = vol->mdb.drCTClpSiz; + else + clumpsz = vol->mdb.drClpSiz; + } + + blocks.xdrNumABlks = clumpsz / vol->mdb.drAlBlkSiz; + + if (v_allocblocks(vol, &blocks) == -1) + goto fail; + + if (f_addextent(file, &blocks) == -1) + { + v_freeblocks(vol, &blocks); + goto fail; + } + + return blocks.xdrNumABlks; + +fail: + return -1; +} + +/* + * NAME: file->trunc() + * DESCRIPTION: release allocation blocks unneeded by a file + */ +int f_trunc(hfsfile *file) +{ + hfsvol *vol = file->vol; + ExtDataRec *extrec; + unsigned long *lglen, *pylen, alblksz, newpylen; + unsigned int dlen, start, end; + node n; + int i; + + if (vol->flags & HFS_VOL_READONLY) + goto done; + + f_getptrs(file, &extrec, &lglen, &pylen); + + alblksz = vol->mdb.drAlBlkSiz; + newpylen = (*lglen / alblksz + (*lglen % alblksz != 0)) * alblksz; + + if (newpylen > *pylen) + ERROR(EIO, "file size exceeds physical length"); + else if (newpylen == *pylen) + goto done; + + dlen = (*pylen - newpylen) / alblksz; + + start = file->fabn; + end = newpylen / alblksz; + + if (start >= end) + { + start = file->fabn = 0; + memcpy(&file->ext, extrec, sizeof(ExtDataRec)); + } + + n.nnum = 0; + i = -1; + + while (start < end) + { + for (i = 0; i < 3; ++i) + { + unsigned int num; + + num = file->ext[i].xdrNumABlks; + start += num; + + if (start >= end) + break; + else if (num == 0) + ERROR(EIO, "empty file extent"); + } + + if (start >= end) + break; + + if (v_extsearch(file, start, &file->ext, &n) <= 0) + goto fail; + + file->fabn = start; + } + + if (start > end) + { + ExtDescriptor blocks; + + file->ext[i].xdrNumABlks -= start - end; + dlen -= start - end; + + blocks.xdrStABN = file->ext[i].xdrStABN + file->ext[i].xdrNumABlks; + blocks.xdrNumABlks = start - end; + + if (v_freeblocks(vol, &blocks) == -1) + goto fail; + } + + *pylen = newpylen; + + file->flags |= HFS_FILE_UPDATE_CATREC; + + do + { + while (dlen && ++i < 3) + { + unsigned int num; + + num = file->ext[i].xdrNumABlks; + start += num; + + if (num == 0) + ERROR(EIO, "empty file extent"); + else if (num > dlen) + ERROR(EIO, "file extents exceed physical size"); + + dlen -= num; + + if (v_freeblocks(vol, &file->ext[i]) == -1) + goto fail; + + file->ext[i].xdrStABN = 0; + file->ext[i].xdrNumABlks = 0; + } + + if (file->fabn) + { + if (n.nnum == 0 && + v_extsearch(file, file->fabn, 0, &n) <= 0) + goto fail; + + if (file->ext[0].xdrNumABlks) + { + if (v_putextrec(&file->ext, &n) == -1) + goto fail; + } + else + { + if (bt_delete(&vol->ext, HFS_NODEREC(n, n.rnum)) == -1) + goto fail; + + n.nnum = 0; + } + } + else + memcpy(extrec, &file->ext, sizeof(ExtDataRec)); + + if (dlen) + { + if (v_extsearch(file, start, &file->ext, &n) <= 0) + goto fail; + + file->fabn = start; + i = -1; + } + } + while (dlen); + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: file->flush() + * DESCRIPTION: flush all pending changes to an open file + */ +int f_flush(hfsfile *file) +{ + hfsvol *vol = file->vol; + + if (vol->flags & HFS_VOL_READONLY) + goto done; + + if (file->flags & HFS_FILE_UPDATE_CATREC) + { + node n; + + file->cat.u.fil.filStBlk = file->cat.u.fil.filExtRec[0].xdrStABN; + file->cat.u.fil.filRStBlk = file->cat.u.fil.filRExtRec[0].xdrStABN; + + if (v_catsearch(vol, file->parid, file->name, 0, 0, &n) <= 0 || + v_putcatrec(&file->cat, &n) == -1) + goto fail; + + file->flags &= ~HFS_FILE_UPDATE_CATREC; + } + +done: + return 0; + +fail: + return -1; +} diff --git a/libhfs/file.h b/libhfs/file.h new file mode 100644 index 0000000..8bd66bb --- /dev/null +++ b/libhfs/file.h @@ -0,0 +1,45 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: file.h,v 1.6 1998/04/11 08:27:12 rob Exp $ + */ + +enum { + fkData = 0x00, + fkRsrc = 0xff +}; + +void f_init(hfsfile *, hfsvol *, long, const char *); +void f_selectfork(hfsfile *, int); +void f_getptrs(hfsfile *, ExtDataRec **, unsigned long **, unsigned long **); + +int f_doblock(hfsfile *, unsigned long, block *, + int (*)(hfsvol *, unsigned int, unsigned int, block *)); + +# define f_getblock(file, num, bp) \ + f_doblock((file), (num), (bp), b_readab) +# define f_putblock(file, num, bp) \ + f_doblock((file), (num), (bp), \ + (int (*)(hfsvol *, unsigned int, unsigned int, block *)) \ + b_writeab) + +int f_addextent(hfsfile *, ExtDescriptor *); +long f_alloc(hfsfile *); + +int f_trunc(hfsfile *); +int f_flush(hfsfile *); diff --git a/libhfs/hfs.c b/libhfs/hfs.c new file mode 100644 index 0000000..f674cce --- /dev/null +++ b/libhfs/hfs.c @@ -0,0 +1,1848 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: hfs.c,v 1.15 1998/11/02 22:09:00 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include +# include + +# include "libhfs.h" +# include "data.h" +# include "block.h" +# include "medium.h" +# include "file.h" +# include "btree.h" +# include "node.h" +# include "record.h" +# include "volume.h" + +const char *hfs_error = "no error"; /* static error string */ + +hfsvol *hfs_mounts; /* linked list of mounted volumes */ + +static +hfsvol *curvol; /* current volume */ + +/* + * NAME: validvname() + * DESCRIPTION: return true if parameter is a valid volume name + */ +static +int validvname(const char *name) +{ + int len; + + len = strlen(name); + if (len < 1) + ERROR(EINVAL, "volume name cannot be empty"); + else if (len > HFS_MAX_VLEN) + ERROR(ENAMETOOLONG, + "volume name can be at most " STR(HFS_MAX_VLEN) " chars"); + + if (strchr(name, ':')) + ERROR(EINVAL, "volume name may not contain colons"); + + return 1; + +fail: + return 0; +} + +/* + * NAME: getvol() + * DESCRIPTION: validate a volume reference + */ +static +int getvol(hfsvol **vol) +{ + if (*vol == 0) + { + if (curvol == 0) + ERROR(EINVAL, "no volume is current"); + + *vol = curvol; + } + + return 0; + +fail: + return -1; +} + +/* High-Level Volume Routines ============================================== */ + +/* + * NAME: hfs->mount() + * DESCRIPTION: open an HFS volume; return volume descriptor or 0 (error) + */ +hfsvol *hfs_mount(const char *path, int pnum, int mode) +{ + hfsvol *vol, *check; + + /* see if the volume is already mounted */ + + for (check = hfs_mounts; check; check = check->next) + { + if (check->pnum == pnum && v_same(check, path) == 1) + { + /* verify compatible read/write mode */ + + if (((check->flags & HFS_VOL_READONLY) && + ! (mode & HFS_MODE_RDWR)) || + (! (check->flags & HFS_VOL_READONLY) && + (mode & (HFS_MODE_RDWR | HFS_MODE_ANY)))) + { + vol = check; + goto done; + } + } + } + + vol = ALLOC(hfsvol, 1); + if (vol == 0) + ERROR(ENOMEM, 0); + + v_init(vol, mode); + + /* open the medium */ + + switch (mode & HFS_MODE_MASK) + { + case HFS_MODE_RDWR: + case HFS_MODE_ANY: + if (v_open(vol, path, HFS_MODE_RDWR) != -1) + break; + + if ((mode & HFS_MODE_MASK) == HFS_MODE_RDWR) + goto fail; + + case HFS_MODE_RDONLY: + default: + vol->flags |= HFS_VOL_READONLY; + + if (v_open(vol, path, HFS_MODE_RDONLY) == -1) + goto fail; + } + + /* mount the volume */ + + if (v_geometry(vol, pnum) == -1 || + v_mount(vol) == -1) + goto fail; + + /* add to linked list of volumes */ + + vol->prev = 0; + vol->next = hfs_mounts; + + if (hfs_mounts) + hfs_mounts->prev = vol; + + hfs_mounts = vol; + +done: + ++vol->refs; + curvol = vol; + + return vol; + +fail: + if (vol) + { + v_close(vol); + FREE(vol); + } + + return 0; +} + +/* + * NAME: hfs->flush() + * DESCRIPTION: flush all pending changes to an HFS volume + */ +int hfs_flush(hfsvol *vol) +{ + hfsfile *file; + + if (getvol(&vol) == -1) + goto fail; + + for (file = vol->files; file; file = file->next) + { + if (f_flush(file) == -1) + goto fail; + } + + if (v_flush(vol) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->flushall() + * DESCRIPTION: flush all pending changes to all mounted HFS volumes + */ +void hfs_flushall(void) +{ + hfsvol *vol; + + for (vol = hfs_mounts; vol; vol = vol->next) + hfs_flush(vol); +} + +/* + * NAME: hfs->umount() + * DESCRIPTION: close an HFS volume + */ +int hfs_umount(hfsvol *vol) +{ + int result = 0; + + if (getvol(&vol) == -1) + goto fail; + + if (--vol->refs) + { + result = v_flush(vol); + goto done; + } + + /* close all open files and directories */ + + while (vol->files) + { + if (hfs_close(vol->files) == -1) + result = -1; + } + + while (vol->dirs) + { + if (hfs_closedir(vol->dirs) == -1) + result = -1; + } + + /* close medium */ + + if (v_close(vol) == -1) + result = -1; + + /* remove from linked list of volumes */ + + if (vol->prev) + vol->prev->next = vol->next; + if (vol->next) + vol->next->prev = vol->prev; + + if (vol == hfs_mounts) + hfs_mounts = vol->next; + if (vol == curvol) + curvol = 0; + + FREE(vol); + +done: + return result; + +fail: + return -1; +} + +/* + * NAME: hfs->umountall() + * DESCRIPTION: unmount all mounted volumes + */ +void hfs_umountall(void) +{ + while (hfs_mounts) + hfs_umount(hfs_mounts); +} + +/* + * NAME: hfs->getvol() + * DESCRIPTION: return a pointer to a mounted volume + */ +hfsvol *hfs_getvol(const char *name) +{ + hfsvol *vol; + + if (name == 0) + return curvol; + + for (vol = hfs_mounts; vol; vol = vol->next) + { + if (d_relstring(name, vol->mdb.drVN) == 0) + return vol; + } + + return 0; +} + +/* + * NAME: hfs->setvol() + * DESCRIPTION: change the current volume + */ +void hfs_setvol(hfsvol *vol) +{ + curvol = vol; +} + +/* + * NAME: hfs->vstat() + * DESCRIPTION: return volume statistics + */ +int hfs_vstat(hfsvol *vol, hfsvolent *ent) +{ + if (getvol(&vol) == -1) + goto fail; + + strcpy(ent->name, vol->mdb.drVN); + + ent->flags = (vol->flags & HFS_VOL_READONLY) ? HFS_ISLOCKED : 0; + + ent->totbytes = vol->mdb.drNmAlBlks * vol->mdb.drAlBlkSiz; + ent->freebytes = vol->mdb.drFreeBks * vol->mdb.drAlBlkSiz; + + ent->alblocksz = vol->mdb.drAlBlkSiz; + ent->clumpsz = vol->mdb.drClpSiz; + + ent->numfiles = vol->mdb.drFilCnt; + ent->numdirs = vol->mdb.drDirCnt; + + ent->crdate = d_ltime(vol->mdb.drCrDate); + ent->mddate = d_ltime(vol->mdb.drLsMod); + ent->bkdate = d_ltime(vol->mdb.drVolBkUp); + + ent->blessed = vol->mdb.drFndrInfo[0]; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->vsetattr() + * DESCRIPTION: change volume attributes + */ +int hfs_vsetattr(hfsvol *vol, hfsvolent *ent) +{ + if (getvol(&vol) == -1) + goto fail; + + if (ent->clumpsz % vol->mdb.drAlBlkSiz != 0) + ERROR(EINVAL, "illegal clump size"); + + /* make sure "blessed" folder exists */ + + if (ent->blessed && + v_getdthread(vol, ent->blessed, 0, 0) <= 0) + ERROR(EINVAL, "illegal blessed folder"); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + vol->mdb.drClpSiz = ent->clumpsz; + + vol->mdb.drCrDate = d_mtime(ent->crdate); + vol->mdb.drLsMod = d_mtime(ent->mddate); + vol->mdb.drVolBkUp = d_mtime(ent->bkdate); + + vol->mdb.drFndrInfo[0] = ent->blessed; + + vol->flags |= HFS_VOL_UPDATE_MDB; + + return 0; + +fail: + return -1; +} + +/* High-Level Directory Routines =========================================== */ + +/* + * NAME: hfs->chdir() + * DESCRIPTION: change current HFS directory + */ +int hfs_chdir(hfsvol *vol, const char *path) +{ + CatDataRec data; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, 0, 0, 0) <= 0) + goto fail; + + if (data.cdrType != cdrDirRec) + ERROR(ENOTDIR, 0); + + vol->cwd = data.u.dir.dirDirID; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->getcwd() + * DESCRIPTION: return the current working directory ID + */ +unsigned long hfs_getcwd(hfsvol *vol) +{ + if (getvol(&vol) == -1) + return 0; + + return vol->cwd; +} + +/* + * NAME: hfs->setcwd() + * DESCRIPTION: set the current working directory ID + */ +int hfs_setcwd(hfsvol *vol, unsigned long id) +{ + if (getvol(&vol) == -1) + goto fail; + + if (id == vol->cwd) + goto done; + + /* make sure the directory exists */ + + if (v_getdthread(vol, id, 0, 0) <= 0) + goto fail; + + vol->cwd = id; + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->dirinfo() + * DESCRIPTION: given a directory ID, return its (name and) parent ID + */ +int hfs_dirinfo(hfsvol *vol, unsigned long *id, char *name) +{ + CatDataRec thread; + + if (getvol(&vol) == -1 || + v_getdthread(vol, *id, &thread, 0) <= 0) + goto fail; + + *id = thread.u.dthd.thdParID; + + if (name) + strcpy(name, thread.u.dthd.thdCName); + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->opendir() + * DESCRIPTION: prepare to read the contents of a directory + */ +hfsdir *hfs_opendir(hfsvol *vol, const char *path) +{ + hfsdir *dir = 0; + CatKeyRec key; + CatDataRec data; + byte pkey[HFS_CATKEYLEN]; + + if (getvol(&vol) == -1) + goto fail; + + dir = ALLOC(hfsdir, 1); + if (dir == 0) + ERROR(ENOMEM, 0); + + dir->vol = vol; + + if (*path == 0) + { + /* meta-directory containing root dirs from all mounted volumes */ + + dir->dirid = 0; + dir->vptr = hfs_mounts; + } + else + { + if (v_resolve(&vol, path, &data, 0, 0, 0) <= 0) + goto fail; + + if (data.cdrType != cdrDirRec) + ERROR(ENOTDIR, 0); + + dir->dirid = data.u.dir.dirDirID; + dir->vptr = 0; + + r_makecatkey(&key, dir->dirid, ""); + r_packcatkey(&key, pkey, 0); + + if (bt_search(&vol->cat, pkey, &dir->n) <= 0) + goto fail; + } + + dir->prev = 0; + dir->next = vol->dirs; + + if (vol->dirs) + vol->dirs->prev = dir; + + vol->dirs = dir; + + return dir; + +fail: + FREE(dir); + return 0; +} + +/* + * NAME: hfs->readdir() + * DESCRIPTION: return the next entry in the directory + */ +int hfs_readdir(hfsdir *dir, hfsdirent *ent) +{ + CatKeyRec key; + CatDataRec data; + const byte *ptr; + + if (dir->dirid == 0) + { + hfsvol *vol; + char cname[HFS_MAX_FLEN + 1]; + + for (vol = hfs_mounts; vol; vol = vol->next) + { + if (vol == dir->vptr) + break; + } + + if (vol == 0) + ERROR(ENOENT, "no more entries"); + + if (v_getdthread(vol, HFS_CNID_ROOTDIR, &data, 0) <= 0 || + v_catsearch(vol, HFS_CNID_ROOTPAR, data.u.dthd.thdCName, + &data, cname, 0) <= 0) + goto fail; + + r_unpackdirent(HFS_CNID_ROOTPAR, cname, &data, ent); + + dir->vptr = vol->next; + + goto done; + } + + if (dir->n.rnum == -1) + ERROR(ENOENT, "no more entries"); + + while (1) + { + ++dir->n.rnum; + + while (dir->n.rnum >= dir->n.nd.ndNRecs) + { + if (dir->n.nd.ndFLink == 0) + { + dir->n.rnum = -1; + ERROR(ENOENT, "no more entries"); + } + + if (bt_getnode(&dir->n, dir->n.bt, dir->n.nd.ndFLink) == -1) + { + dir->n.rnum = -1; + goto fail; + } + + dir->n.rnum = 0; + } + + ptr = HFS_NODEREC(dir->n, dir->n.rnum); + + r_unpackcatkey(ptr, &key); + + if (key.ckrParID != dir->dirid) + { + dir->n.rnum = -1; + ERROR(ENOENT, "no more entries"); + } + + r_unpackcatdata(HFS_RECDATA(ptr), &data); + + switch (data.cdrType) + { + case cdrDirRec: + case cdrFilRec: + r_unpackdirent(key.ckrParID, key.ckrCName, &data, ent); + goto done; + + case cdrThdRec: + case cdrFThdRec: + break; + + default: + dir->n.rnum = -1; + ERROR(EIO, "unexpected directory entry found"); + } + } + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->closedir() + * DESCRIPTION: stop reading a directory + */ +int hfs_closedir(hfsdir *dir) +{ + hfsvol *vol = dir->vol; + + if (dir->prev) + dir->prev->next = dir->next; + if (dir->next) + dir->next->prev = dir->prev; + if (dir == vol->dirs) + vol->dirs = dir->next; + + FREE(dir); + + return 0; +} + +/* High-Level File Routines ================================================ */ + +/* + * NAME: hfs->create() + * DESCRIPTION: create and open a new file + */ +hfsfile *hfs_create(hfsvol *vol, const char *path, + const char *type, const char *creator) +{ + hfsfile *file = 0; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + CatKeyRec key; + byte record[HFS_MAX_CATRECLEN]; + unsigned reclen; + int found; + + if (getvol(&vol) == -1) + goto fail; + + file = ALLOC(hfsfile, 1); + if (file == 0) + ERROR(ENOMEM, 0); + + found = v_resolve(&vol, path, &file->cat, &parid, name, 0); + if (found == -1 || parid == 0) + goto fail; + + if (found) + ERROR(EEXIST, 0); + + if (parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* create file `name' in parent `parid' */ + + if (bt_space(&vol->cat, 1) == -1) + goto fail; + + f_init(file, vol, vol->mdb.drNxtCNID++, name); + vol->flags |= HFS_VOL_UPDATE_MDB; + + file->parid = parid; + + /* create catalog record */ + + file->cat.u.fil.filUsrWds.fdType = + d_getsl((const unsigned char *) type); + file->cat.u.fil.filUsrWds.fdCreator = + d_getsl((const unsigned char *) creator); + + file->cat.u.fil.filCrDat = d_mtime(time(0)); + file->cat.u.fil.filMdDat = file->cat.u.fil.filCrDat; + + r_makecatkey(&key, file->parid, file->name); + r_packcatrec(&key, &file->cat, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1 || + v_adjvalence(vol, file->parid, 0, 1) == -1) + goto fail; + + /* package file handle for user */ + + file->next = vol->files; + + if (vol->files) + vol->files->prev = file; + + vol->files = file; + + return file; + +fail: + FREE(file); + return 0; +} + +/* + * NAME: hfs->open() + * DESCRIPTION: prepare a file for I/O + */ +hfsfile *hfs_open(hfsvol *vol, const char *path) +{ + hfsfile *file = 0; + + if (getvol(&vol) == -1) + goto fail; + + file = ALLOC(hfsfile, 1); + if (file == 0) + ERROR(ENOMEM, 0); + + if (v_resolve(&vol, path, &file->cat, &file->parid, file->name, 0) <= 0) + goto fail; + + if (file->cat.cdrType != cdrFilRec) + ERROR(EISDIR, 0); + + /* package file handle for user */ + + file->vol = vol; + file->flags = 0; + + f_selectfork(file, fkData); + + file->prev = 0; + file->next = vol->files; + + if (vol->files) + vol->files->prev = file; + + vol->files = file; + + return file; + +fail: + FREE(file); + return 0; +} + +/* + * NAME: hfs->setfork() + * DESCRIPTION: select file fork for I/O operations + */ +int hfs_setfork(hfsfile *file, int fork) +{ + int result = 0; + + if (f_trunc(file) == -1) + result = -1; + + f_selectfork(file, fork ? fkRsrc : fkData); + + return result; +} + +/* + * NAME: hfs->getfork() + * DESCRIPTION: return the current fork for I/O operations + */ +int hfs_getfork(hfsfile *file) +{ + return file->fork != fkData; +} + +/* + * NAME: hfs->read() + * DESCRIPTION: read from an open file + */ +unsigned long hfs_read(hfsfile *file, void *buf, unsigned long len) +{ + unsigned long *lglen, count; + byte *ptr = buf; + + f_getptrs(file, 0, &lglen, 0); + + if (file->pos + len > *lglen) + len = *lglen - file->pos; + + count = len; + while (count) + { + unsigned long bnum, offs, chunk; + + bnum = file->pos >> HFS_BLOCKSZ_BITS; + offs = file->pos & (HFS_BLOCKSZ - 1); + + chunk = HFS_BLOCKSZ - offs; + if (chunk > count) + chunk = count; + + if (offs == 0 && chunk == HFS_BLOCKSZ) + { + if (f_getblock(file, bnum, (block *) ptr) == -1) + goto fail; + } + else + { + block b; + + if (f_getblock(file, bnum, &b) == -1) + goto fail; + + memcpy(ptr, b + offs, chunk); + } + + ptr += chunk; + + file->pos += chunk; + count -= chunk; + } + + return len; + +fail: + return -1; +} + +/* + * NAME: hfs->write() + * DESCRIPTION: write to an open file + */ +unsigned long hfs_write(hfsfile *file, const void *buf, unsigned long len) +{ + unsigned long *lglen, *pylen, count; + const byte *ptr = buf; + + if (file->vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + f_getptrs(file, 0, &lglen, &pylen); + + count = len; + + /* set flag to update (at least) the modification time */ + + if (count) + { + file->cat.u.fil.filMdDat = d_mtime(time(0)); + file->flags |= HFS_FILE_UPDATE_CATREC; + } + + while (count) + { + unsigned long bnum, offs, chunk; + + bnum = file->pos >> HFS_BLOCKSZ_BITS; + offs = file->pos & (HFS_BLOCKSZ - 1); + + chunk = HFS_BLOCKSZ - offs; + if (chunk > count) + chunk = count; + + if (file->pos + chunk > *pylen) + { + if (bt_space(&file->vol->ext, 1) == -1 || + f_alloc(file) == -1) + goto fail; + } + + if (offs == 0 && chunk == HFS_BLOCKSZ) + { + if (f_putblock(file, bnum, (block *) ptr) == -1) + goto fail; + } + else + { + block b; + + if (f_getblock(file, bnum, &b) == -1) + goto fail; + + memcpy(b + offs, ptr, chunk); + + if (f_putblock(file, bnum, &b) == -1) + goto fail; + } + + ptr += chunk; + + file->pos += chunk; + count -= chunk; + + if (file->pos > *lglen) + *lglen = file->pos; + } + + return len; + +fail: + return -1; +} + +/* + * NAME: hfs->truncate() + * DESCRIPTION: truncate an open file + */ +int hfs_truncate(hfsfile *file, unsigned long len) +{ + unsigned long *lglen; + + f_getptrs(file, 0, &lglen, 0); + + if (*lglen > len) + { + if (file->vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + *lglen = len; + + file->cat.u.fil.filMdDat = d_mtime(time(0)); + file->flags |= HFS_FILE_UPDATE_CATREC; + + if (file->pos > len) + file->pos = len; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->seek() + * DESCRIPTION: change file seek pointer + */ +unsigned long hfs_seek(hfsfile *file, long offset, int from) +{ + unsigned long *lglen, newpos; + + f_getptrs(file, 0, &lglen, 0); + + switch (from) + { + case HFS_SEEK_SET: + newpos = (offset < 0) ? 0 : offset; + break; + + case HFS_SEEK_CUR: + if (offset < 0 && (unsigned long) -offset > file->pos) + newpos = 0; + else + newpos = file->pos + offset; + break; + + case HFS_SEEK_END: + if (offset < 0 && (unsigned long) -offset > *lglen) + newpos = 0; + else + newpos = *lglen + offset; + break; + + default: + ERROR(EINVAL, 0); + } + + if (newpos > *lglen) + newpos = *lglen; + + file->pos = newpos; + + return newpos; + +fail: + return -1; +} + +/* + * NAME: hfs->close() + * DESCRIPTION: close a file + */ +int hfs_close(hfsfile *file) +{ + hfsvol *vol = file->vol; + int result = 0; + + if (f_trunc(file) == -1 || + f_flush(file) == -1) + result = -1; + + if (file->prev) + file->prev->next = file->next; + if (file->next) + file->next->prev = file->prev; + if (file == vol->files) + vol->files = file->next; + + FREE(file); + + return result; +} + +/* High-Level Catalog Routines ============================================= */ + +/* + * NAME: hfs->stat() + * DESCRIPTION: return catalog information for an arbitrary path + */ +int hfs_stat(hfsvol *vol, const char *path, hfsdirent *ent) +{ + CatDataRec data; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, &parid, name, 0) <= 0) + goto fail; + + r_unpackdirent(parid, name, &data, ent); + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->fstat() + * DESCRIPTION: return catalog information for an open file + */ +int hfs_fstat(hfsfile *file, hfsdirent *ent) +{ + r_unpackdirent(file->parid, file->name, &file->cat, ent); + + return 0; +} + +/* + * NAME: hfs->setattr() + * DESCRIPTION: change a file's attributes + */ +int hfs_setattr(hfsvol *vol, const char *path, const hfsdirent *ent) +{ + CatDataRec data; + node n; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, 0, 0, &n) <= 0) + goto fail; + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + r_packdirent(&data, ent); + + return v_putcatrec(&data, &n); + +fail: + return -1; +} + +/* + * NAME: hfs->fsetattr() + * DESCRIPTION: change an open file's attributes + */ +int hfs_fsetattr(hfsfile *file, const hfsdirent *ent) +{ + if (file->vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + r_packdirent(&file->cat, ent); + + file->flags |= HFS_FILE_UPDATE_CATREC; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->mkdir() + * DESCRIPTION: create a new directory + */ +int hfs_mkdir(hfsvol *vol, const char *path) +{ + CatDataRec data; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + int found; + + if (getvol(&vol) == -1) + goto fail; + + found = v_resolve(&vol, path, &data, &parid, name, 0); + if (found == -1 || parid == 0) + goto fail; + + if (found) + ERROR(EEXIST, 0); + + if (parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + return v_mkdir(vol, parid, name); + +fail: + return -1; +} + +/* + * NAME: hfs->rmdir() + * DESCRIPTION: delete an empty directory + */ +int hfs_rmdir(hfsvol *vol, const char *path) +{ + CatKeyRec key; + CatDataRec data; + unsigned long parid; + char name[HFS_MAX_FLEN + 1]; + byte pkey[HFS_CATKEYLEN]; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &data, &parid, name, 0) <= 0) + goto fail; + + if (data.cdrType != cdrDirRec) + ERROR(ENOTDIR, 0); + + if (data.u.dir.dirVal != 0) + ERROR(ENOTEMPTY, 0); + + if (parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* delete directory record */ + + r_makecatkey(&key, parid, name); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1) + goto fail; + + /* delete thread record */ + + r_makecatkey(&key, data.u.dir.dirDirID, ""); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1 || + v_adjvalence(vol, parid, 1, -1) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->delete() + * DESCRIPTION: remove both forks of a file + */ +int hfs_delete(hfsvol *vol, const char *path) +{ + hfsfile file; + CatKeyRec key; + byte pkey[HFS_CATKEYLEN]; + int found; + + if (getvol(&vol) == -1 || + v_resolve(&vol, path, &file.cat, &file.parid, file.name, 0) <= 0) + goto fail; + + if (file.cat.cdrType != cdrFilRec) + ERROR(EISDIR, 0); + + if (file.parid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, 0); + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* free allocation blocks */ + + file.vol = vol; + file.flags = 0; + + file.cat.u.fil.filLgLen = 0; + file.cat.u.fil.filRLgLen = 0; + + f_selectfork(&file, fkData); + if (f_trunc(&file) == -1) + goto fail; + + f_selectfork(&file, fkRsrc); + if (f_trunc(&file) == -1) + goto fail; + + /* delete file record */ + + r_makecatkey(&key, file.parid, file.name); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1 || + v_adjvalence(vol, file.parid, 0, -1) == -1) + goto fail; + + /* delete file thread, if any */ + + found = v_getfthread(vol, file.cat.u.fil.filFlNum, 0, 0); + if (found == -1) + goto fail; + + if (found) + { + r_makecatkey(&key, file.cat.u.fil.filFlNum, ""); + r_packcatkey(&key, pkey, 0); + + if (bt_delete(&vol->cat, pkey) == -1) + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: hfs->rename() + * DESCRIPTION: change the name of and/or move a file or directory + */ +int hfs_rename(hfsvol *vol, const char *srcpath, const char *dstpath) +{ + hfsvol *srcvol; + CatDataRec src, dst; + unsigned long srcid, dstid; + CatKeyRec key; + char srcname[HFS_MAX_FLEN + 1], dstname[HFS_MAX_FLEN + 1]; + byte record[HFS_MAX_CATRECLEN]; + unsigned int reclen; + int found, isdir, moving; + node n; + + if (getvol(&vol) == -1 || + v_resolve(&vol, srcpath, &src, &srcid, srcname, 0) <= 0) + goto fail; + + isdir = (src.cdrType == cdrDirRec); + srcvol = vol; + + found = v_resolve(&vol, dstpath, &dst, &dstid, dstname, 0); + if (found == -1) + goto fail; + + if (vol != srcvol) + ERROR(EINVAL, "can't move across volumes"); + + if (dstid == 0) + ERROR(ENOENT, "bad destination path"); + + if (found && + dst.cdrType == cdrDirRec && + dst.u.dir.dirDirID != src.u.dir.dirDirID) + { + dstid = dst.u.dir.dirDirID; + strcpy(dstname, srcname); + + found = v_catsearch(vol, dstid, dstname, 0, 0, 0); + if (found == -1) + goto fail; + } + + moving = (srcid != dstid); + + if (found) + { + const char *ptr; + + ptr = strrchr(dstpath, ':'); + if (ptr == 0) + ptr = dstpath; + else + ++ptr; + + if (*ptr) + strcpy(dstname, ptr); + + if (! moving && strcmp(srcname, dstname) == 0) + goto done; /* source and destination are identical */ + + if (moving || d_relstring(srcname, dstname)) + ERROR(EEXIST, "can't use destination name"); + } + + /* can't move anything into the root directory's parent */ + + if (moving && dstid == HFS_CNID_ROOTPAR) + ERROR(EINVAL, "can't move above root directory"); + + if (moving && isdir) + { + unsigned long id; + + /* can't move root directory anywhere */ + + if (src.u.dir.dirDirID == HFS_CNID_ROOTDIR) + ERROR(EINVAL, "can't move root directory"); + + /* make sure we aren't trying to move a directory inside itself */ + + for (id = dstid; id != HFS_CNID_ROOTDIR; id = dst.u.dthd.thdParID) + { + if (id == src.u.dir.dirDirID) + ERROR(EINVAL, "can't move directory inside itself"); + + if (v_getdthread(vol, id, &dst, 0) <= 0) + goto fail; + } + } + + if (vol->flags & HFS_VOL_READONLY) + ERROR(EROFS, 0); + + /* change volume name */ + + if (dstid == HFS_CNID_ROOTPAR) + { + if (! validvname(dstname)) + goto fail; + + strcpy(vol->mdb.drVN, dstname); + vol->flags |= HFS_VOL_UPDATE_MDB; + } + + /* remove source record */ + + r_makecatkey(&key, srcid, srcname); + r_packcatkey(&key, record, 0); + + if (bt_delete(&vol->cat, record) == -1) + goto fail; + + /* insert destination record */ + + r_makecatkey(&key, dstid, dstname); + r_packcatrec(&key, &src, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1) + goto fail; + + /* update thread record */ + + if (isdir) + { + if (v_getdthread(vol, src.u.dir.dirDirID, &dst, &n) <= 0) + goto fail; + + dst.u.dthd.thdParID = dstid; + strcpy(dst.u.dthd.thdCName, dstname); + + if (v_putcatrec(&dst, &n) == -1) + goto fail; + } + else + { + found = v_getfthread(vol, src.u.fil.filFlNum, &dst, &n); + if (found == -1) + goto fail; + + if (found) + { + dst.u.fthd.fthdParID = dstid; + strcpy(dst.u.fthd.fthdCName, dstname); + + if (v_putcatrec(&dst, &n) == -1) + goto fail; + } + } + + /* update directory valences */ + + if (moving) + { + if (v_adjvalence(vol, srcid, isdir, -1) == -1 || + v_adjvalence(vol, dstid, isdir, 1) == -1) + goto fail; + } + +done: + return 0; + +fail: + return -1; +} + +/* High-Level Media Routines =============================================== */ + +/* + * NAME: hfs->zero() + * DESCRIPTION: initialize medium with new/empty DDR and partition map + */ +int hfs_zero(const char *path, unsigned int maxparts, unsigned long *blocks) +{ + hfsvol vol; + + v_init(&vol, HFS_OPT_NOCACHE); + + if (maxparts < 1) + ERROR(EINVAL, "must allow at least 1 partition"); + + if (v_open(&vol, path, HFS_MODE_RDWR) == -1 || + v_geometry(&vol, 0) == -1) + goto fail; + + if (m_zeroddr(&vol) == -1 || + m_zeropm(&vol, 1 + maxparts) == -1) + goto fail; + + if (blocks) + { + Partition map; + int found; + + found = m_findpmentry(&vol, "Apple_Free", &map, 0); + if (found == -1) + goto fail; + + if (! found) + ERROR(EIO, "unable to determine free partition space"); + + *blocks = map.pmPartBlkCnt; + } + + if (v_close(&vol) == -1) + goto fail; + + return 0; + +fail: + v_close(&vol); + return -1; +} + +/* + * NAME: hfs->mkpart() + * DESCRIPTION: create a new HFS partition + */ +int hfs_mkpart(const char *path, unsigned long len) +{ + hfsvol vol; + + v_init(&vol, HFS_OPT_NOCACHE); + + if (v_open(&vol, path, HFS_MODE_RDWR) == -1) + goto fail; + + if (m_mkpart(&vol, "MacOS", "Apple_HFS", len) == -1) + goto fail; + + if (v_close(&vol) == -1) + goto fail; + + return 0; + +fail: + v_close(&vol); + return -1; +} + +/* + * NAME: hfs->nparts() + * DESCRIPTION: return the number of HFS partitions in the medium + */ +int hfs_nparts(const char *path) +{ + hfsvol vol; + int nparts, found; + Partition map; + unsigned long bnum = 0; + + v_init(&vol, HFS_OPT_NOCACHE); + + if (v_open(&vol, path, HFS_MODE_RDONLY) == -1) + goto fail; + + nparts = 0; + while (1) + { + found = m_findpmentry(&vol, "Apple_HFS", &map, &bnum); + if (found == -1) + goto fail; + + if (! found) + break; + + ++nparts; + } + + if (v_close(&vol) == -1) + goto fail; + + return nparts; + +fail: + v_close(&vol); + return -1; +} + +/* + * NAME: compare() + * DESCRIPTION: comparison function for qsort of blocks to be spared + */ +static +int compare(const unsigned int *n1, const unsigned int *n2) +{ + return *n1 - *n2; +} + +/* + * NAME: hfs->format() + * DESCRIPTION: write a new filesystem + */ +int hfs_format(const char *path, int pnum, int mode, const char *vname, + unsigned int nbadblocks, const unsigned long badblocks[]) +{ + hfsvol vol; + btree *ext = &vol.ext; + btree *cat = &vol.cat; + unsigned int i, *badalloc = 0; + + v_init(&vol, mode); + + if (! validvname(vname)) + goto fail; + + if (v_open(&vol, path, HFS_MODE_RDWR) == -1 || + v_geometry(&vol, pnum) == -1) + goto fail; + + /* initialize volume geometry */ + + vol.lpa = 1 + ((vol.vlen - 6) >> 16); + + if (vol.flags & HFS_OPT_2048) + vol.lpa = (vol.lpa + 3) & ~3; + + vol.vbmsz = (vol.vlen / vol.lpa + 0x0fff) >> 12; + + vol.mdb.drSigWord = HFS_SIGWORD; + vol.mdb.drCrDate = d_mtime(time(0)); + vol.mdb.drLsMod = vol.mdb.drCrDate; + vol.mdb.drAtrb = 0; + vol.mdb.drNmFls = 0; + vol.mdb.drVBMSt = 3; + vol.mdb.drAllocPtr = 0; + + vol.mdb.drAlBlkSiz = vol.lpa << HFS_BLOCKSZ_BITS; + vol.mdb.drClpSiz = vol.mdb.drAlBlkSiz << 2; + vol.mdb.drAlBlSt = vol.mdb.drVBMSt + vol.vbmsz; + + if (vol.flags & HFS_OPT_2048) + vol.mdb.drAlBlSt = ((vol.vstart & 3) + vol.mdb.drAlBlSt + 3) & ~3; + + vol.mdb.drNmAlBlks = (vol.vlen - 2 - vol.mdb.drAlBlSt) / vol.lpa; + + vol.mdb.drNxtCNID = HFS_CNID_ROOTDIR; /* modified later */ + vol.mdb.drFreeBks = vol.mdb.drNmAlBlks; + + strcpy(vol.mdb.drVN, vname); + + vol.mdb.drVolBkUp = 0; + vol.mdb.drVSeqNum = 0; + vol.mdb.drWrCnt = 0; + + vol.mdb.drXTClpSiz = vol.mdb.drNmAlBlks / 128 * vol.mdb.drAlBlkSiz; + vol.mdb.drCTClpSiz = vol.mdb.drXTClpSiz; + + vol.mdb.drNmRtDirs = 0; + vol.mdb.drFilCnt = 0; + vol.mdb.drDirCnt = -1; /* incremented when root directory is created */ + + for (i = 0; i < 8; ++i) + vol.mdb.drFndrInfo[i] = 0; + + vol.mdb.drEmbedSigWord = 0x0000; + vol.mdb.drEmbedExtent.xdrStABN = 0; + vol.mdb.drEmbedExtent.xdrNumABlks = 0; + + /* vol.mdb.drXTFlSize */ + /* vol.mdb.drCTFlSize */ + + /* vol.mdb.drXTExtRec[0..2] */ + /* vol.mdb.drCTExtRec[0..2] */ + + vol.flags |= HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_ALTMDB; + + /* initialize volume bitmap */ + + vol.vbm = ALLOC(block, vol.vbmsz); + if (vol.vbm == 0) + ERROR(ENOMEM, 0); + + memset(vol.vbm, 0, vol.vbmsz << HFS_BLOCKSZ_BITS); + + vol.flags |= HFS_VOL_UPDATE_VBM; + + /* perform initial bad block sparing */ + + if (nbadblocks > 0) + { + if (nbadblocks * 4 > vol.vlen) + ERROR(EINVAL, "volume contains too many bad blocks"); + + badalloc = ALLOC(unsigned int, nbadblocks); + if (badalloc == 0) + ERROR(ENOMEM, 0); + + if (vol.mdb.drNmAlBlks == 1594) + vol.mdb.drFreeBks = --vol.mdb.drNmAlBlks; + + for (i = 0; i < nbadblocks; ++i) + { + unsigned long bnum; + unsigned int anum; + + bnum = badblocks[i]; + + if (bnum < vol.mdb.drAlBlSt || bnum == vol.vlen - 2) + ERROR(EINVAL, "can't spare critical bad block"); + else if (bnum >= vol.vlen) + ERROR(EINVAL, "bad block not in volume"); + + anum = (bnum - vol.mdb.drAlBlSt) / vol.lpa; + + if (anum < vol.mdb.drNmAlBlks) + BMSET(vol.vbm, anum); + + badalloc[i] = anum; + } + + vol.mdb.drAtrb |= HFS_ATRB_BBSPARED; + } + + /* create extents overflow file */ + + n_init(&ext->hdrnd, ext, ndHdrNode, 0); + + ext->hdrnd.nnum = 0; + ext->hdrnd.nd.ndNRecs = 3; + ext->hdrnd.roff[1] = 0x078; + ext->hdrnd.roff[2] = 0x0f8; + ext->hdrnd.roff[3] = 0x1f8; + + memset(HFS_NODEREC(ext->hdrnd, 1), 0, 128); + + ext->hdr.bthDepth = 0; + ext->hdr.bthRoot = 0; + ext->hdr.bthNRecs = 0; + ext->hdr.bthFNode = 0; + ext->hdr.bthLNode = 0; + ext->hdr.bthNodeSize = HFS_BLOCKSZ; + ext->hdr.bthKeyLen = 0x07; + ext->hdr.bthNNodes = 0; + ext->hdr.bthFree = 0; + for (i = 0; i < 76; ++i) + ext->hdr.bthResv[i] = 0; + + ext->map = ALLOC(byte, HFS_MAP1SZ); + if (ext->map == 0) + ERROR(ENOMEM, 0); + + memset(ext->map, 0, HFS_MAP1SZ); + BMSET(ext->map, 0); + + ext->mapsz = HFS_MAP1SZ; + ext->flags = HFS_BT_UPDATE_HDR; + + /* create catalog file */ + + n_init(&cat->hdrnd, cat, ndHdrNode, 0); + + cat->hdrnd.nnum = 0; + cat->hdrnd.nd.ndNRecs = 3; + cat->hdrnd.roff[1] = 0x078; + cat->hdrnd.roff[2] = 0x0f8; + cat->hdrnd.roff[3] = 0x1f8; + + memset(HFS_NODEREC(cat->hdrnd, 1), 0, 128); + + cat->hdr.bthDepth = 0; + cat->hdr.bthRoot = 0; + cat->hdr.bthNRecs = 0; + cat->hdr.bthFNode = 0; + cat->hdr.bthLNode = 0; + cat->hdr.bthNodeSize = HFS_BLOCKSZ; + cat->hdr.bthKeyLen = 0x25; + cat->hdr.bthNNodes = 0; + cat->hdr.bthFree = 0; + for (i = 0; i < 76; ++i) + cat->hdr.bthResv[i] = 0; + + cat->map = ALLOC(byte, HFS_MAP1SZ); + if (cat->map == 0) + ERROR(ENOMEM, 0); + + memset(cat->map, 0, HFS_MAP1SZ); + BMSET(cat->map, 0); + + cat->mapsz = HFS_MAP1SZ; + cat->flags = HFS_BT_UPDATE_HDR; + + /* allocate space for header nodes (and initial extents) */ + + if (bt_space(ext, 1) == -1 || + bt_space(cat, 1) == -1) + goto fail; + + --ext->hdr.bthFree; + --cat->hdr.bthFree; + + /* create extent records for bad blocks */ + + if (nbadblocks > 0) + { + hfsfile bbfile; + ExtDescriptor extent; + ExtDataRec *extrec; + ExtKeyRec key; + byte record[HFS_MAX_EXTRECLEN]; + unsigned int reclen; + + f_init(&bbfile, &vol, HFS_CNID_BADALLOC, "bad blocks"); + + qsort(badalloc, nbadblocks, sizeof(*badalloc), + (int (*)(const void *, const void *)) compare); + + for (i = 0; i < nbadblocks; ++i) + { + if (i == 0 || badalloc[i] != extent.xdrStABN) + { + extent.xdrStABN = badalloc[i]; + extent.xdrNumABlks = 1; + + if (extent.xdrStABN < vol.mdb.drNmAlBlks && + f_addextent(&bbfile, &extent) == -1) + goto fail; + } + } + + /* flush local extents into extents overflow file */ + + f_getptrs(&bbfile, &extrec, 0, 0); + + r_makeextkey(&key, bbfile.fork, bbfile.cat.u.fil.filFlNum, 0); + r_packextrec(&key, extrec, record, &reclen); + + if (bt_insert(&vol.ext, record, reclen) == -1) + goto fail; + } + + vol.flags |= HFS_VOL_MOUNTED; + + /* create root directory */ + + if (v_mkdir(&vol, HFS_CNID_ROOTPAR, vname) == -1) + goto fail; + + vol.mdb.drNxtCNID = 16; /* first CNID not reserved by Apple */ + + /* write boot blocks */ + + if (m_zerobb(&vol) == -1) + goto fail; + + /* zero other unused space, if requested */ + + if (vol.flags & HFS_OPT_ZERO) + { + block b; + unsigned long bnum; + + memset(&b, 0, sizeof(b)); + + /* between MDB and VBM (never) */ + + for (bnum = 3; bnum < vol.mdb.drVBMSt; ++bnum) + b_writelb(&vol, bnum, &b); + + /* between VBM and first allocation block (sometimes if HFS_OPT_2048) */ + + for (bnum = vol.mdb.drVBMSt + vol.vbmsz; bnum < vol.mdb.drAlBlSt; ++bnum) + b_writelb(&vol, bnum, &b); + + /* between last allocation block and alternate MDB (sometimes) */ + + for (bnum = vol.mdb.drAlBlSt + vol.mdb.drNmAlBlks * vol.lpa; + bnum < vol.vlen - 2; ++bnum) + b_writelb(&vol, bnum, &b); + + /* final block (always) */ + + b_writelb(&vol, vol.vlen - 1, &b); + } + + /* flush remaining state and close volume */ + + if (v_close(&vol) == -1) + goto fail; + + FREE(badalloc); + + return 0; + +fail: + v_close(&vol); + + FREE(badalloc); + + return -1; +} diff --git a/libhfs/hfs.h b/libhfs/hfs.h new file mode 100644 index 0000000..ef9c943 --- /dev/null +++ b/libhfs/hfs.h @@ -0,0 +1,182 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: hfs.h,v 1.11 1998/11/02 22:09:01 rob Exp $ + */ + +# include + +# define HFS_BLOCKSZ 512 +# define HFS_BLOCKSZ_BITS 9 + +# define HFS_MAX_FLEN 31 +# define HFS_MAX_VLEN 27 + +typedef struct _hfsvol_ hfsvol; +typedef struct _hfsfile_ hfsfile; +typedef struct _hfsdir_ hfsdir; + +typedef struct { + char name[HFS_MAX_VLEN + 1]; /* name of volume (MacOS Standard Roman) */ + int flags; /* volume flags */ + + unsigned long totbytes; /* total bytes on volume */ + unsigned long freebytes; /* free bytes on volume */ + + unsigned long alblocksz; /* volume allocation block size */ + unsigned long clumpsz; /* default file clump size */ + + unsigned long numfiles; /* number of files in volume */ + unsigned long numdirs; /* number of directories in volume */ + + time_t crdate; /* volume creation date */ + time_t mddate; /* last volume modification date */ + time_t bkdate; /* last volume backup date */ + + unsigned long blessed; /* CNID of MacOS System Folder */ +} hfsvolent; + +typedef struct { + char name[HFS_MAX_FLEN + 1]; /* catalog name (MacOS Standard Roman) */ + int flags; /* bit flags */ + unsigned long cnid; /* catalog node id (CNID) */ + unsigned long parid; /* CNID of parent directory */ + + time_t crdate; /* date of creation */ + time_t mddate; /* date of last modification */ + time_t bkdate; /* date of last backup */ + + short fdflags; /* Macintosh Finder flags */ + short fdcomment; /* Macintosh Comment ID */ + + struct { + signed short v; /* Finder icon vertical coordinate */ + signed short h; /* horizontal coordinate */ + } fdlocation; + + union { + struct { + unsigned long dsize; /* size of data fork */ + unsigned long rsize; /* size of resource fork */ + + char type[5]; /* file type code (plus null) */ + char creator[5]; /* file creator code (plus null) */ + } file; + + struct { + unsigned short valence; /* number of items in directory */ + + struct { + signed short top; /* top edge of folder's rectangle */ + signed short left; /* left edge */ + signed short bottom; /* bottom edge */ + signed short right; /* right edge */ + } rect; + } dir; + } u; +} hfsdirent; + +# define HFS_ISDIR 0x0001 +# define HFS_ISLOCKED 0x0002 + +# define HFS_CNID_ROOTPAR 1 +# define HFS_CNID_ROOTDIR 2 +# define HFS_CNID_EXT 3 +# define HFS_CNID_CAT 4 +# define HFS_CNID_BADALLOC 5 + +# define HFS_FNDR_ISONDESK (1 << 0) +# define HFS_FNDR_COLOR 0x0e +# define HFS_FNDR_COLORRESERVED (1 << 4) +# define HFS_FNDR_REQUIRESSWITCHLAUNCH (1 << 5) +# define HFS_FNDR_ISSHARED (1 << 6) +# define HFS_FNDR_HASNOINITS (1 << 7) +# define HFS_FNDR_HASBEENINITED (1 << 8) +# define HFS_FNDR_RESERVED (1 << 9) +# define HFS_FNDR_HASCUSTOMICON (1 << 10) +# define HFS_FNDR_ISSTATIONERY (1 << 11) +# define HFS_FNDR_NAMELOCKED (1 << 12) +# define HFS_FNDR_HASBUNDLE (1 << 13) +# define HFS_FNDR_ISINVISIBLE (1 << 14) +# define HFS_FNDR_ISALIAS (1 << 15) + +extern const char *hfs_error; +extern const unsigned char hfs_charorder[]; + +# define HFS_MODE_RDONLY 0 +# define HFS_MODE_RDWR 1 +# define HFS_MODE_ANY 2 + +# define HFS_MODE_MASK 0x0003 + +# define HFS_OPT_NOCACHE 0x0100 +# define HFS_OPT_2048 0x0200 +# define HFS_OPT_ZERO 0x0400 +# define HFS_OPT_DC42HEADER 0x0800 + +# define HFS_SEEK_SET 0 +# define HFS_SEEK_CUR 1 +# define HFS_SEEK_END 2 + +hfsvol *hfs_mount(const char *, int, int); +int hfs_flush(hfsvol *); +void hfs_flushall(void); +int hfs_umount(hfsvol *); +void hfs_umountall(void); +hfsvol *hfs_getvol(const char *); +void hfs_setvol(hfsvol *); + +int hfs_vstat(hfsvol *, hfsvolent *); +int hfs_vsetattr(hfsvol *, hfsvolent *); + +int hfs_chdir(hfsvol *, const char *); +unsigned long hfs_getcwd(hfsvol *); +int hfs_setcwd(hfsvol *, unsigned long); +int hfs_dirinfo(hfsvol *, unsigned long *, char *); + +hfsdir *hfs_opendir(hfsvol *, const char *); +int hfs_readdir(hfsdir *, hfsdirent *); +int hfs_closedir(hfsdir *); + +hfsfile *hfs_create(hfsvol *, const char *, const char *, const char *); +hfsfile *hfs_open(hfsvol *, const char *); +int hfs_setfork(hfsfile *, int); +int hfs_getfork(hfsfile *); +unsigned long hfs_read(hfsfile *, void *, unsigned long); +unsigned long hfs_write(hfsfile *, const void *, unsigned long); +int hfs_truncate(hfsfile *, unsigned long); +unsigned long hfs_seek(hfsfile *, long, int); +int hfs_close(hfsfile *); + +int hfs_stat(hfsvol *, const char *, hfsdirent *); +int hfs_fstat(hfsfile *, hfsdirent *); +int hfs_setattr(hfsvol *, const char *, const hfsdirent *); +int hfs_fsetattr(hfsfile *, const hfsdirent *); + +int hfs_mkdir(hfsvol *, const char *); +int hfs_rmdir(hfsvol *, const char *); + +int hfs_delete(hfsvol *, const char *); +int hfs_rename(hfsvol *, const char *, const char *); + +int hfs_zero(const char *, unsigned int, unsigned long *); +int hfs_mkpart(const char *, unsigned long); +int hfs_nparts(const char *); + +int hfs_format(const char *, int, int, + const char *, unsigned int, const unsigned long []); diff --git a/libhfs/libhfs.h b/libhfs/libhfs.h new file mode 100644 index 0000000..7072e95 --- /dev/null +++ b/libhfs/libhfs.h @@ -0,0 +1,227 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: libhfs.h,v 1.7 1998/11/02 22:09:02 rob Exp $ + */ + +# include "hfs.h" +# include "apple.h" + +extern int errno; + +# define ERROR(code, str) \ + do { hfs_error = (str), errno = (code); goto fail; } while (0) + +# ifdef DEBUG +# define ASSERT(cond) do { if (! (cond)) abort(); } while (0) +# else +# define ASSERT(cond) /* nothing */ +# endif + +# define SIZE(type, n) ((size_t) (sizeof(type) * (n))) +# define ALLOC(type, n) ((type *) malloc(SIZE(type, n))) +# define ALLOCX(type, n) ((n) ? ALLOC(type, n) : (type *) 0) +# define FREE(ptr) ((ptr) ? (void) free((void *) ptr) : (void) 0) + +# define REALLOC(ptr, type, n) \ + ((type *) ((ptr) ? realloc(ptr, SIZE(type, n)) : malloc(SIZE(type, n)))) +# define REALLOCX(ptr, type, n) \ + ((n) ? REALLOC(ptr, type, n) : (FREE(ptr), (type *) 0)) + +# define BMTST(bm, num) \ + (((const byte *) (bm))[(num) >> 3] & (0x80 >> ((num) & 0x07))) +# define BMSET(bm, num) \ + (((byte *) (bm))[(num) >> 3] |= (0x80 >> ((num) & 0x07))) +# define BMCLR(bm, num) \ + (((byte *) (bm))[(num) >> 3] &= ~(0x80 >> ((num) & 0x07))) + +# define STRINGIZE(x) #x +# define STR(x) STRINGIZE(x) + +typedef unsigned char byte; +typedef byte block[HFS_BLOCKSZ]; + +typedef struct _bucket_ { + int flags; /* bit flags */ + unsigned int count; /* number of times this block is requested */ + + unsigned long bnum; /* logical block number */ + block *data; /* pointer to block contents */ + + struct _bucket_ *cnext; /* next bucket in cache chain */ + struct _bucket_ *cprev; /* previous bucket in cache chain */ + + struct _bucket_ *hnext; /* next bucket in hash chain */ + struct _bucket_ **hprev; /* previous bucket's pointer to this bucket */ +} bucket; + +# define HFS_BUCKET_INUSE 0x01 +# define HFS_BUCKET_DIRTY 0x02 + +# define HFS_CACHESZ 128 +# define HFS_HASHSZ 32 +# define HFS_BLOCKBUFSZ 16 + +typedef struct { + struct _hfsvol_ *vol; /* volume to which cache belongs */ + bucket *tail; /* end of bucket chain */ + + unsigned int hits; /* number of cache hits */ + unsigned int misses; /* number of cache misses */ + + bucket chain[HFS_CACHESZ]; /* cache bucket chain */ + bucket *hash[HFS_HASHSZ]; /* hash table for bucket chain */ + + block pool[HFS_CACHESZ]; /* physical blocks in cache */ +} bcache; + +# define HFS_MAP1SZ 256 +# define HFS_MAPXSZ 492 + +# define HFS_NODEREC(nd, rnum) ((nd).data + (nd).roff[rnum]) +# define HFS_RECLEN(nd, rnum) ((nd).roff[(rnum) + 1] - (nd).roff[rnum]) + +# define HFS_RECKEYLEN(ptr) (*(const byte *) (ptr)) +# define HFS_RECKEYSKIP(ptr) ((size_t) ((1 + HFS_RECKEYLEN(ptr) + 1) & ~1)) +# define HFS_RECDATA(ptr) ((ptr) + HFS_RECKEYSKIP(ptr)) + +# define HFS_SETKEYLEN(ptr, x) (*(byte *) (ptr) = (x)) + +# define HFS_CATDATALEN sizeof(CatDataRec) +# define HFS_EXTDATALEN sizeof(ExtDataRec) +# define HFS_MAX_DATALEN (HFS_CATDATALEN > HFS_EXTDATALEN ? \ + HFS_CATDATALEN : HFS_EXTDATALEN) + +# define HFS_CATKEYLEN sizeof(CatKeyRec) +# define HFS_EXTKEYLEN sizeof(ExtKeyRec) +# define HFS_MAX_KEYLEN (HFS_CATKEYLEN > HFS_EXTKEYLEN ? \ + HFS_CATKEYLEN : HFS_EXTKEYLEN) + +# define HFS_MAX_CATRECLEN (HFS_CATKEYLEN + HFS_CATDATALEN) +# define HFS_MAX_EXTRECLEN (HFS_EXTKEYLEN + HFS_EXTDATALEN) +# define HFS_MAX_RECLEN (HFS_MAX_KEYLEN + HFS_MAX_DATALEN) + +# define HFS_SIGWORD 0x4244 +# define HFS_SIGWORD_MFS ((Integer) 0xd2d7) + +# define HFS_ATRB_BUSY (1 << 6) +# define HFS_ATRB_HLOCKED (1 << 7) +# define HFS_ATRB_UMOUNTED (1 << 8) +# define HFS_ATRB_BBSPARED (1 << 9) +# define HFS_ATRB_BVINCONSIS (1 << 11) +# define HFS_ATRB_COPYPROT (1 << 14) +# define HFS_ATRB_SLOCKED (1 << 15) + +struct _hfsfile_ { + struct _hfsvol_ *vol; /* pointer to volume descriptor */ + unsigned long parid; /* parent directory ID of this file */ + char name[HFS_MAX_FLEN + 1]; /* catalog name of this file */ + CatDataRec cat; /* catalog information */ + ExtDataRec ext; /* current extent record */ + unsigned int fabn; /* starting file allocation block number */ + int fork; /* current selected fork for I/O */ + unsigned long pos; /* current file seek pointer */ + int flags; /* bit flags */ + + struct _hfsfile_ *prev; + struct _hfsfile_ *next; +}; + +# define HFS_FILE_UPDATE_CATREC 0x01 + +# define HFS_MAX_NRECS 35 /* maximum based on minimum record size */ + +typedef struct _node_ { + struct _btree_ *bt; /* btree to which this node belongs */ + unsigned long nnum; /* node index */ + NodeDescriptor nd; /* node descriptor */ + int rnum; /* current record index */ + UInteger roff[HFS_MAX_NRECS + 1]; + /* record offsets */ + block data; /* raw contents of node */ +} node; + +struct _hfsdir_ { + struct _hfsvol_ *vol; /* associated volume */ + unsigned long dirid; /* directory ID of interest (or 0) */ + + node n; /* current B*-tree node */ + struct _hfsvol_ *vptr; /* current volume pointer */ + + struct _hfsdir_ *prev; + struct _hfsdir_ *next; +}; + +typedef void (*keyunpackfunc)(const byte *, void *); +typedef int (*keycomparefunc)(const void *, const void *); + +typedef struct _btree_ { + hfsfile f; /* subset file information */ + node hdrnd; /* header node */ + BTHdrRec hdr; /* header record */ + byte *map; /* usage bitmap */ + unsigned long mapsz; /* number of bytes in bitmap */ + int flags; /* bit flags */ + + keyunpackfunc keyunpack; /* key unpacking function */ + keycomparefunc keycompare; /* key comparison function */ +} btree; + +# define HFS_BT_UPDATE_HDR 0x01 + +struct _hfsvol_ { + void *priv; /* OS-dependent private descriptor data */ + long base; /* base of volume, for disk images with header */ + int flags; /* bit flags */ + + int pnum; /* ordinal HFS partition number */ + unsigned long vstart; /* logical block offset to start of volume */ + unsigned long vlen; /* number of logical blocks in volume */ + unsigned int lpa; /* number of logical blocks per allocation block */ + + bcache *cache; /* cache of recently used blocks */ + + MDB mdb; /* master directory block */ + block *vbm; /* volume bitmap */ + unsigned short vbmsz; /* number of blocks in bitmap */ + + btree ext; /* B*-tree control block for extents overflow file */ + btree cat; /* B*-tree control block for catalog file */ + + unsigned long cwd; /* directory id of current working directory */ + + int refs; /* number of external references to this volume */ + hfsfile *files; /* list of open files */ + hfsdir *dirs; /* list of open directories */ + + struct _hfsvol_ *prev; + struct _hfsvol_ *next; +}; + +# define HFS_VOL_OPEN 0x0001 +# define HFS_VOL_MOUNTED 0x0002 +# define HFS_VOL_READONLY 0x0004 +# define HFS_VOL_USINGCACHE 0x0008 + +# define HFS_VOL_UPDATE_MDB 0x0010 +# define HFS_VOL_UPDATE_ALTMDB 0x0020 +# define HFS_VOL_UPDATE_VBM 0x0040 + +# define HFS_VOL_OPT_MASK 0xff00 + +extern hfsvol *hfs_mounts; diff --git a/libhfs/low.c b/libhfs/low.c new file mode 100644 index 0000000..f5e2ad1 --- /dev/null +++ b/libhfs/low.c @@ -0,0 +1,470 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: low.c,v 1.8 1998/11/02 22:09:03 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include + +# include "libhfs.h" +# include "low.h" +# include "data.h" +# include "block.h" +# include "file.h" + +/* + * NAME: low->getddr() + * DESCRIPTION: read a driver descriptor record + */ +int l_getddr(hfsvol *vol, Block0 *ddr) +{ + block b; + const byte *ptr = b; + int i; + + if (b_readpb(vol, 0, &b, 1) == -1) + goto fail; + + d_fetchsw(&ptr, &ddr->sbSig); + d_fetchsw(&ptr, &ddr->sbBlkSize); + d_fetchsl(&ptr, &ddr->sbBlkCount); + d_fetchsw(&ptr, &ddr->sbDevType); + d_fetchsw(&ptr, &ddr->sbDevId); + d_fetchsl(&ptr, &ddr->sbData); + d_fetchsw(&ptr, &ddr->sbDrvrCount); + d_fetchsl(&ptr, &ddr->ddBlock); + d_fetchsw(&ptr, &ddr->ddSize); + d_fetchsw(&ptr, &ddr->ddType); + + for (i = 0; i < 243; ++i) + d_fetchsw(&ptr, &ddr->ddPad[i]); + + ASSERT(ptr - b == HFS_BLOCKSZ); + + return 0; + +fail: + return -1; +} + +/* + * NAME: low->putddr() + * DESCRIPTION: write a driver descriptor record + */ +int l_putddr(hfsvol *vol, const Block0 *ddr) +{ + block b; + byte *ptr = b; + int i; + + d_storesw(&ptr, ddr->sbSig); + d_storesw(&ptr, ddr->sbBlkSize); + d_storesl(&ptr, ddr->sbBlkCount); + d_storesw(&ptr, ddr->sbDevType); + d_storesw(&ptr, ddr->sbDevId); + d_storesl(&ptr, ddr->sbData); + d_storesw(&ptr, ddr->sbDrvrCount); + d_storesl(&ptr, ddr->ddBlock); + d_storesw(&ptr, ddr->ddSize); + d_storesw(&ptr, ddr->ddType); + + for (i = 0; i < 243; ++i) + d_storesw(&ptr, ddr->ddPad[i]); + + ASSERT(ptr - b == HFS_BLOCKSZ); + + if (b_writepb(vol, 0, &b, 1) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: low->getpmentry() + * DESCRIPTION: read a partition map entry + */ +int l_getpmentry(hfsvol *vol, Partition *map, unsigned long bnum) +{ + block b; + const byte *ptr = b; + int i; + + if (b_readpb(vol, bnum, &b, 1) == -1) + goto fail; + + d_fetchsw(&ptr, &map->pmSig); + d_fetchsw(&ptr, &map->pmSigPad); + d_fetchsl(&ptr, &map->pmMapBlkCnt); + d_fetchsl(&ptr, &map->pmPyPartStart); + d_fetchsl(&ptr, &map->pmPartBlkCnt); + + strncpy((char *) map->pmPartName, (const char *) ptr, 32); + map->pmPartName[32] = 0; + ptr += 32; + + strncpy((char *) map->pmParType, (const char *) ptr, 32); + map->pmParType[32] = 0; + ptr += 32; + + d_fetchsl(&ptr, &map->pmLgDataStart); + d_fetchsl(&ptr, &map->pmDataCnt); + d_fetchsl(&ptr, &map->pmPartStatus); + d_fetchsl(&ptr, &map->pmLgBootStart); + d_fetchsl(&ptr, &map->pmBootSize); + d_fetchsl(&ptr, &map->pmBootAddr); + d_fetchsl(&ptr, &map->pmBootAddr2); + d_fetchsl(&ptr, &map->pmBootEntry); + d_fetchsl(&ptr, &map->pmBootEntry2); + d_fetchsl(&ptr, &map->pmBootCksum); + + strncpy((char *) map->pmProcessor, (const char *) ptr, 16); + map->pmProcessor[16] = 0; + ptr += 16; + + for (i = 0; i < 188; ++i) + d_fetchsw(&ptr, &map->pmPad[i]); + + ASSERT(ptr - b == HFS_BLOCKSZ); + + return 0; + +fail: + return -1; +} + +/* + * NAME: low->putpmentry() + * DESCRIPTION: write a partition map entry + */ +int l_putpmentry(hfsvol *vol, const Partition *map, unsigned long bnum) +{ + block b; + byte *ptr = b; + int i; + + d_storesw(&ptr, map->pmSig); + d_storesw(&ptr, map->pmSigPad); + d_storesl(&ptr, map->pmMapBlkCnt); + d_storesl(&ptr, map->pmPyPartStart); + d_storesl(&ptr, map->pmPartBlkCnt); + + memset(ptr, 0, 32); + strncpy((char *) ptr, (const char *) map->pmPartName, 32); + ptr += 32; + + memset(ptr, 0, 32); + strncpy((char *) ptr, (const char *) map->pmParType, 32); + ptr += 32; + + d_storesl(&ptr, map->pmLgDataStart); + d_storesl(&ptr, map->pmDataCnt); + d_storesl(&ptr, map->pmPartStatus); + d_storesl(&ptr, map->pmLgBootStart); + d_storesl(&ptr, map->pmBootSize); + d_storesl(&ptr, map->pmBootAddr); + d_storesl(&ptr, map->pmBootAddr2); + d_storesl(&ptr, map->pmBootEntry); + d_storesl(&ptr, map->pmBootEntry2); + d_storesl(&ptr, map->pmBootCksum); + + memset(ptr, 0, 16); + strncpy((char *) ptr, (const char *) map->pmProcessor, 16); + ptr += 16; + + for (i = 0; i < 188; ++i) + d_storesw(&ptr, map->pmPad[i]); + + ASSERT(ptr - b == HFS_BLOCKSZ); + + if (b_writepb(vol, bnum, &b, 1) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: low->getbb() + * DESCRIPTION: read a volume's boot blocks + */ +int l_getbb(hfsvol *vol, BootBlkHdr *bb, byte *bootcode) +{ + block b; + const byte *ptr = b; + + if (b_readlb(vol, 0, &b) == -1) + goto fail; + + d_fetchsw(&ptr, &bb->bbID); + d_fetchsl(&ptr, &bb->bbEntry); + d_fetchsw(&ptr, &bb->bbVersion); + d_fetchsw(&ptr, &bb->bbPageFlags); + + d_fetchstr(&ptr, bb->bbSysName, sizeof(bb->bbSysName)); + d_fetchstr(&ptr, bb->bbShellName, sizeof(bb->bbShellName)); + d_fetchstr(&ptr, bb->bbDbg1Name, sizeof(bb->bbDbg1Name)); + d_fetchstr(&ptr, bb->bbDbg2Name, sizeof(bb->bbDbg2Name)); + d_fetchstr(&ptr, bb->bbScreenName, sizeof(bb->bbScreenName)); + d_fetchstr(&ptr, bb->bbHelloName, sizeof(bb->bbHelloName)); + d_fetchstr(&ptr, bb->bbScrapName, sizeof(bb->bbScrapName)); + + d_fetchsw(&ptr, &bb->bbCntFCBs); + d_fetchsw(&ptr, &bb->bbCntEvts); + d_fetchsl(&ptr, &bb->bb128KSHeap); + d_fetchsl(&ptr, &bb->bb256KSHeap); + d_fetchsl(&ptr, &bb->bbSysHeapSize); + d_fetchsw(&ptr, &bb->filler); + d_fetchsl(&ptr, &bb->bbSysHeapExtra); + d_fetchsl(&ptr, &bb->bbSysHeapFract); + + ASSERT(ptr - b == 148); + + if (bootcode) + { + memcpy(bootcode, ptr, HFS_BOOTCODE1LEN); + + if (b_readlb(vol, 1, &b) == -1) + goto fail; + + memcpy(bootcode + HFS_BOOTCODE1LEN, b, HFS_BOOTCODE2LEN); + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: low->putbb() + * DESCRIPTION: write a volume's boot blocks + */ +int l_putbb(hfsvol *vol, const BootBlkHdr *bb, const byte *bootcode) +{ + block b; + byte *ptr = b; + + d_storesw(&ptr, bb->bbID); + d_storesl(&ptr, bb->bbEntry); + d_storesw(&ptr, bb->bbVersion); + d_storesw(&ptr, bb->bbPageFlags); + + d_storestr(&ptr, bb->bbSysName, sizeof(bb->bbSysName)); + d_storestr(&ptr, bb->bbShellName, sizeof(bb->bbShellName)); + d_storestr(&ptr, bb->bbDbg1Name, sizeof(bb->bbDbg1Name)); + d_storestr(&ptr, bb->bbDbg2Name, sizeof(bb->bbDbg2Name)); + d_storestr(&ptr, bb->bbScreenName, sizeof(bb->bbScreenName)); + d_storestr(&ptr, bb->bbHelloName, sizeof(bb->bbHelloName)); + d_storestr(&ptr, bb->bbScrapName, sizeof(bb->bbScrapName)); + + d_storesw(&ptr, bb->bbCntFCBs); + d_storesw(&ptr, bb->bbCntEvts); + d_storesl(&ptr, bb->bb128KSHeap); + d_storesl(&ptr, bb->bb256KSHeap); + d_storesl(&ptr, bb->bbSysHeapSize); + d_storesw(&ptr, bb->filler); + d_storesl(&ptr, bb->bbSysHeapExtra); + d_storesl(&ptr, bb->bbSysHeapFract); + + ASSERT(ptr - b == 148); + + if (bootcode) + memcpy(ptr, bootcode, HFS_BOOTCODE1LEN); + else + memset(ptr, 0, HFS_BOOTCODE1LEN); + + if (b_writelb(vol, 0, &b) == -1) + goto fail; + + if (bootcode) + memcpy(&b, bootcode + HFS_BOOTCODE1LEN, HFS_BOOTCODE2LEN); + else + memset(&b, 0, HFS_BOOTCODE2LEN); + + if (b_writelb(vol, 1, &b) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: low->getmdb() + * DESCRIPTION: read a master directory block + */ +int l_getmdb(hfsvol *vol, MDB *mdb, int backup) +{ + block b; + const byte *ptr = b; + int i; + + if (b_readlb(vol, backup ? vol->vlen - 2 : 2, &b) == -1) + goto fail; + + d_fetchsw(&ptr, &mdb->drSigWord); + d_fetchsl(&ptr, &mdb->drCrDate); + d_fetchsl(&ptr, &mdb->drLsMod); + d_fetchsw(&ptr, &mdb->drAtrb); + d_fetchuw(&ptr, &mdb->drNmFls); + d_fetchuw(&ptr, &mdb->drVBMSt); + d_fetchuw(&ptr, &mdb->drAllocPtr); + d_fetchuw(&ptr, &mdb->drNmAlBlks); + d_fetchul(&ptr, &mdb->drAlBlkSiz); + d_fetchul(&ptr, &mdb->drClpSiz); + d_fetchuw(&ptr, &mdb->drAlBlSt); + d_fetchsl(&ptr, &mdb->drNxtCNID); + d_fetchuw(&ptr, &mdb->drFreeBks); + + d_fetchstr(&ptr, mdb->drVN, sizeof(mdb->drVN)); + + ASSERT(ptr - b == 64); + + d_fetchsl(&ptr, &mdb->drVolBkUp); + d_fetchsw(&ptr, &mdb->drVSeqNum); + d_fetchul(&ptr, &mdb->drWrCnt); + d_fetchul(&ptr, &mdb->drXTClpSiz); + d_fetchul(&ptr, &mdb->drCTClpSiz); + d_fetchuw(&ptr, &mdb->drNmRtDirs); + d_fetchul(&ptr, &mdb->drFilCnt); + d_fetchul(&ptr, &mdb->drDirCnt); + + for (i = 0; i < 8; ++i) + d_fetchsl(&ptr, &mdb->drFndrInfo[i]); + + ASSERT(ptr - b == 124); + + d_fetchuw(&ptr, &mdb->drEmbedSigWord); + d_fetchuw(&ptr, &mdb->drEmbedExtent.xdrStABN); + d_fetchuw(&ptr, &mdb->drEmbedExtent.xdrNumABlks); + + d_fetchul(&ptr, &mdb->drXTFlSize); + + for (i = 0; i < 3; ++i) + { + d_fetchuw(&ptr, &mdb->drXTExtRec[i].xdrStABN); + d_fetchuw(&ptr, &mdb->drXTExtRec[i].xdrNumABlks); + } + + ASSERT(ptr - b == 146); + + d_fetchul(&ptr, &mdb->drCTFlSize); + + for (i = 0; i < 3; ++i) + { + d_fetchuw(&ptr, &mdb->drCTExtRec[i].xdrStABN); + d_fetchuw(&ptr, &mdb->drCTExtRec[i].xdrNumABlks); + } + + ASSERT(ptr - b == 162); + + return 0; + +fail: + return -1; +} + +/* + * NAME: low->putmdb() + * DESCRIPTION: write master directory block(s) + */ +int l_putmdb(hfsvol *vol, const MDB *mdb, int backup) +{ + block b; + byte *ptr = b; + int i; + + d_storesw(&ptr, mdb->drSigWord); + d_storesl(&ptr, mdb->drCrDate); + d_storesl(&ptr, mdb->drLsMod); + d_storesw(&ptr, mdb->drAtrb); + d_storeuw(&ptr, mdb->drNmFls); + d_storeuw(&ptr, mdb->drVBMSt); + d_storeuw(&ptr, mdb->drAllocPtr); + d_storeuw(&ptr, mdb->drNmAlBlks); + d_storeul(&ptr, mdb->drAlBlkSiz); + d_storeul(&ptr, mdb->drClpSiz); + d_storeuw(&ptr, mdb->drAlBlSt); + d_storesl(&ptr, mdb->drNxtCNID); + d_storeuw(&ptr, mdb->drFreeBks); + + d_storestr(&ptr, mdb->drVN, sizeof(mdb->drVN)); + + ASSERT(ptr - b == 64); + + d_storesl(&ptr, mdb->drVolBkUp); + d_storesw(&ptr, mdb->drVSeqNum); + d_storeul(&ptr, mdb->drWrCnt); + d_storeul(&ptr, mdb->drXTClpSiz); + d_storeul(&ptr, mdb->drCTClpSiz); + d_storeuw(&ptr, mdb->drNmRtDirs); + d_storeul(&ptr, mdb->drFilCnt); + d_storeul(&ptr, mdb->drDirCnt); + + for (i = 0; i < 8; ++i) + d_storesl(&ptr, mdb->drFndrInfo[i]); + + ASSERT(ptr - b == 124); + + d_storeuw(&ptr, mdb->drEmbedSigWord); + d_storeuw(&ptr, mdb->drEmbedExtent.xdrStABN); + d_storeuw(&ptr, mdb->drEmbedExtent.xdrNumABlks); + + d_storeul(&ptr, mdb->drXTFlSize); + + for (i = 0; i < 3; ++i) + { + d_storeuw(&ptr, mdb->drXTExtRec[i].xdrStABN); + d_storeuw(&ptr, mdb->drXTExtRec[i].xdrNumABlks); + } + + ASSERT(ptr - b == 146); + + d_storeul(&ptr, mdb->drCTFlSize); + + for (i = 0; i < 3; ++i) + { + d_storeuw(&ptr, mdb->drCTExtRec[i].xdrStABN); + d_storeuw(&ptr, mdb->drCTExtRec[i].xdrNumABlks); + } + + ASSERT(ptr - b == 162); + + memset(ptr, 0, HFS_BLOCKSZ - (ptr - b)); + + if (b_writelb(vol, 2, &b) == -1 || + (backup && b_writelb(vol, vol->vlen - 2, &b) == -1)) + goto fail; + + return 0; + +fail: + return -1; +} diff --git a/libhfs/low.h b/libhfs/low.h new file mode 100644 index 0000000..03db9bb --- /dev/null +++ b/libhfs/low.h @@ -0,0 +1,44 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: low.h,v 1.6 1998/04/11 08:27:13 rob Exp $ + */ + +# define HFS_DDR_SIGWORD 0x4552 + +# define HFS_PM_SIGWORD 0x504d +# define HFS_PM_SIGWORD_OLD 0x5453 + +# define HFS_BB_SIGWORD 0x4c4b + +# define HFS_BOOTCODE1LEN (HFS_BLOCKSZ - 148) +# define HFS_BOOTCODE2LEN HFS_BLOCKSZ + +# define HFS_BOOTCODELEN (HFS_BOOTCODE1LEN + HFS_BOOTCODE2LEN) + +int l_getddr(hfsvol *, Block0 *); +int l_putddr(hfsvol *, const Block0 *); + +int l_getpmentry(hfsvol *, Partition *, unsigned long); +int l_putpmentry(hfsvol *, const Partition *, unsigned long); + +int l_getbb(hfsvol *, BootBlkHdr *, byte *); +int l_putbb(hfsvol *, const BootBlkHdr *, const byte *); + +int l_getmdb(hfsvol *, MDB *, int); +int l_putmdb(hfsvol *, const MDB *, int); diff --git a/libhfs/medium.c b/libhfs/medium.c new file mode 100644 index 0000000..baa5a3c --- /dev/null +++ b/libhfs/medium.c @@ -0,0 +1,318 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: medium.c,v 1.4 1998/11/02 22:09:04 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include + +# include "libhfs.h" +# include "block.h" +# include "low.h" +# include "medium.h" + +/* Driver Descriptor Record Routines ======================================= */ + +/* + * NAME: medium->zeroddr() + * DESCRIPTION: write a new/empty driver descriptor record + */ +int m_zeroddr(hfsvol *vol) +{ + Block0 ddr; + int i; + + ASSERT(vol->pnum == 0 && vol->vlen != 0); + + ddr.sbSig = HFS_DDR_SIGWORD; + ddr.sbBlkSize = HFS_BLOCKSZ; + ddr.sbBlkCount = vol->vlen; + + ddr.sbDevType = 0; + ddr.sbDevId = 0; + ddr.sbData = 0; + + ddr.sbDrvrCount = 0; + + ddr.ddBlock = 0; + ddr.ddSize = 0; + ddr.ddType = 0; + + for (i = 0; i < 243; ++i) + ddr.ddPad[i] = 0; + + return l_putddr(vol, &ddr); +} + +/* Partition Map Routines ================================================== */ + +/* + * NAME: medium->zeropm() + * DESCRIPTION: write new/empty partition map + */ +int m_zeropm(hfsvol *vol, unsigned int maxparts) +{ + Partition map; + unsigned int i; + + ASSERT(vol->pnum == 0 && vol->vlen != 0); + + if (maxparts < 2) + ERROR(EINVAL, "must allow at least 2 partitions"); + + /* first entry: partition map itself */ + + map.pmSig = HFS_PM_SIGWORD; + map.pmSigPad = 0; + map.pmMapBlkCnt = 2; + + map.pmPyPartStart = 1; + map.pmPartBlkCnt = maxparts; + + strcpy((char *) map.pmPartName, "Apple"); + strcpy((char *) map.pmParType, "Apple_partition_map"); + + map.pmLgDataStart = 0; + map.pmDataCnt = map.pmPartBlkCnt; + + map.pmPartStatus = 0; + + map.pmLgBootStart = 0; + map.pmBootSize = 0; + map.pmBootAddr = 0; + map.pmBootAddr2 = 0; + map.pmBootEntry = 0; + map.pmBootEntry2 = 0; + map.pmBootCksum = 0; + + strcpy((char *) map.pmProcessor, ""); + + for (i = 0; i < 188; ++i) + map.pmPad[i] = 0; + + if (l_putpmentry(vol, &map, 1) == -1) + goto fail; + + /* second entry: rest of medium */ + + map.pmPyPartStart = 1 + maxparts; + map.pmPartBlkCnt = vol->vlen - 1 - maxparts; + + strcpy((char *) map.pmPartName, "Extra"); + strcpy((char *) map.pmParType, "Apple_Free"); + + map.pmDataCnt = map.pmPartBlkCnt; + + if (l_putpmentry(vol, &map, 2) == -1) + goto fail; + + /* zero rest of partition map's partition */ + + if (maxparts > 2) + { + block b; + + memset(&b, 0, sizeof(b)); + + for (i = 3; i <= maxparts; ++i) + { + if (b_writepb(vol, i, &b, 1) == -1) + goto fail; + } + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: medium->findpmentry() + * DESCRIPTION: locate a partition map entry + */ +int m_findpmentry(hfsvol *vol, const char *type, + Partition *map, unsigned long *start) +{ + unsigned long bnum; + int found = 0; + + if (start && *start > 0) + { + bnum = *start; + + if (bnum++ >= (unsigned long) map->pmMapBlkCnt) + ERROR(EINVAL, "partition not found"); + } + else + bnum = 1; + + while (1) + { + if (l_getpmentry(vol, map, bnum) == -1) + { + found = -1; + goto fail; + } + + if (map->pmSig != HFS_PM_SIGWORD) + { + found = -1; + + if (map->pmSig == HFS_PM_SIGWORD_OLD) + ERROR(EINVAL, "old partition map format not supported"); + else + ERROR(EINVAL, "invalid partition map"); + } + + if (strcmp((char *) map->pmParType, type) == 0) + { + found = 1; + goto done; + } + + if (bnum++ >= (unsigned long) map->pmMapBlkCnt) + ERROR(EINVAL, "partition not found"); + } + +done: + if (start) + *start = bnum; + +fail: + return found; +} + +/* + * NAME: medium->mkpart() + * DESCRIPTION: create a new partition from available free space + */ +int m_mkpart(hfsvol *vol, + const char *name, const char *type, unsigned long len) +{ + Partition map; + unsigned int nparts, maxparts; + unsigned long bnum, start, remain; + int found; + + if (strlen(name) > 32 || + strlen(type) > 32) + ERROR(EINVAL, "partition name/type can each be at most 32 chars"); + + if (len == 0) + ERROR(EINVAL, "partition length must be > 0"); + + found = m_findpmentry(vol, "Apple_partition_map", &map, 0); + if (found == -1) + goto fail; + + if (! found) + ERROR(EIO, "cannot find partition map's partition"); + + nparts = map.pmMapBlkCnt; + maxparts = map.pmPartBlkCnt; + + bnum = 0; + do + { + found = m_findpmentry(vol, "Apple_Free", &map, &bnum); + if (found == -1) + goto fail; + + if (! found) + ERROR(ENOSPC, "no available partitions"); + } + while (len > (unsigned long) map.pmPartBlkCnt); + + start = (unsigned long) map.pmPyPartStart + len; + remain = (unsigned long) map.pmPartBlkCnt - len; + + if (remain && nparts >= maxparts) + ERROR(EINVAL, "must allocate all blocks in free space"); + + map.pmPartBlkCnt = len; + + strcpy((char *) map.pmPartName, name); + strcpy((char *) map.pmParType, type); + + map.pmLgDataStart = 0; + map.pmDataCnt = len; + + map.pmPartStatus = 0; + + if (l_putpmentry(vol, &map, bnum) == -1) + goto fail; + + if (remain) + { + map.pmPyPartStart = start; + map.pmPartBlkCnt = remain; + + strcpy((char *) map.pmPartName, "Extra"); + strcpy((char *) map.pmParType, "Apple_Free"); + + map.pmDataCnt = remain; + + if (l_putpmentry(vol, &map, ++nparts) == -1) + goto fail; + + for (bnum = 1; bnum <= nparts; ++bnum) + { + if (l_getpmentry(vol, &map, bnum) == -1) + goto fail; + + map.pmMapBlkCnt = nparts; + + if (l_putpmentry(vol, &map, bnum) == -1) + goto fail; + } + } + + return 0; + +fail: + return -1; +} + +/* Boot Blocks Routines ==================================================== */ + +/* + * NAME: medium->zerobb() + * DESCRIPTION: write new/empty volume boot blocks + */ +int m_zerobb(hfsvol *vol) +{ + block b; + + memset(&b, 0, sizeof(b)); + + if (b_writelb(vol, 0, &b) == -1 || + b_writelb(vol, 1, &b) == -1) + goto fail; + + return 0; + +fail: + return -1; +} diff --git a/libhfs/medium.h b/libhfs/medium.h new file mode 100644 index 0000000..5580a9e --- /dev/null +++ b/libhfs/medium.h @@ -0,0 +1,42 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: medium.h,v 1.3 1998/04/11 08:27:13 rob Exp $ + */ + +/* + * Partition Types: + * + * "Apple_partition_map" partition map + * "Apple_Driver" device driver + * "Apple_Driver43" SCSI Manager 4.3 device driver + * "Apple_MFS" Macintosh 64K ROM filesystem + * "Apple_HFS" Macintosh hierarchical filesystem + * "Apple_Unix_SVR2" Unix filesystem + * "Apple_PRODOS" ProDOS filesystem + * "Apple_Free" unused + * "Apple_Scratch" empty + */ + +int m_zeroddr(hfsvol *); + +int m_zeropm(hfsvol *, unsigned int); +int m_findpmentry(hfsvol *, const char *, Partition *, unsigned long *); +int m_mkpart(hfsvol *, const char *, const char *, unsigned long); + +int m_zerobb(hfsvol *); diff --git a/libhfs/memcmp.c b/libhfs/memcmp.c new file mode 100644 index 0000000..f1816e4 --- /dev/null +++ b/libhfs/memcmp.c @@ -0,0 +1,50 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: memcmp.c,v 1.6 1998/04/11 16:22:48 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include + +/* + * NAME: memcmp() + * DESCRIPTION: compare memory areas + */ +int memcmp(const void *s1, const void *s2, size_t n) +{ + register const unsigned char *c1, *c2; + + c1 = s1; + c2 = s2; + + while (n--) + { + register int diff; + + diff = *c1++ - *c2++; + + if (diff) + return diff; + } + + return 0; +} diff --git a/libhfs/node.c b/libhfs/node.c new file mode 100644 index 0000000..90268e0 --- /dev/null +++ b/libhfs/node.c @@ -0,0 +1,473 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: node.c,v 1.9 1998/11/02 22:09:05 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include + +# include "libhfs.h" +# include "node.h" +# include "data.h" +# include "btree.h" + +/* total bytes used by records (NOT including record offsets) */ + +# define NODEUSED(n) \ + ((size_t) ((n).roff[(n).nd.ndNRecs] - (n).roff[0])) + +/* total bytes available for new records (INCLUDING record offsets) */ + +# define NODEFREE(n) \ + ((size_t) (HFS_BLOCKSZ - (n).roff[(n).nd.ndNRecs] - \ + 2 * ((n).nd.ndNRecs + 1))) + +/* + * NAME: node->init() + * DESCRIPTION: construct an empty node + */ +void n_init(node *np, btree *bt, int type, int height) +{ + np->bt = bt; + np->nnum = (unsigned long) -1; + + np->nd.ndFLink = 0; + np->nd.ndBLink = 0; + np->nd.ndType = type; + np->nd.ndNHeight = height; + np->nd.ndNRecs = 0; + np->nd.ndResv2 = 0; + + np->rnum = -1; + np->roff[0] = 0x00e; + + memset(&np->data, 0, sizeof(np->data)); +} + +/* + * NAME: node->new() + * DESCRIPTION: allocate a new b*-tree node + */ +int n_new(node *np) +{ + btree *bt = np->bt; + unsigned long num; + + if (bt->hdr.bthFree == 0) + ERROR(EIO, "b*-tree full"); + + num = 0; + while (num < bt->hdr.bthNNodes && BMTST(bt->map, num)) + ++num; + + if (num == bt->hdr.bthNNodes) + ERROR(EIO, "free b*-tree node not found"); + + np->nnum = num; + + BMSET(bt->map, num); + --bt->hdr.bthFree; + + bt->flags |= HFS_BT_UPDATE_HDR; + + return 0; + +fail: + return -1; +} + +/* + * NAME: node->free() + * DESCRIPTION: deallocate and remove a b*-tree node + */ +int n_free(node *np) +{ + btree *bt = np->bt; + node sib; + + if (bt->hdr.bthFNode == np->nnum) + bt->hdr.bthFNode = np->nd.ndFLink; + + if (bt->hdr.bthLNode == np->nnum) + bt->hdr.bthLNode = np->nd.ndBLink; + + if (np->nd.ndFLink > 0) + { + if (bt_getnode(&sib, bt, np->nd.ndFLink) == -1) + goto fail; + + sib.nd.ndBLink = np->nd.ndBLink; + + if (bt_putnode(&sib) == -1) + goto fail; + } + + if (np->nd.ndBLink > 0) + { + if (bt_getnode(&sib, bt, np->nd.ndBLink) == -1) + goto fail; + + sib.nd.ndFLink = np->nd.ndFLink; + + if (bt_putnode(&sib) == -1) + goto fail; + } + + BMCLR(bt->map, np->nnum); + ++bt->hdr.bthFree; + + bt->flags |= HFS_BT_UPDATE_HDR; + + return 0; + +fail: + return -1; +} + +/* + * NAME: compact() + * DESCRIPTION: clean up a node, removing deleted records + */ +static +void compact(node *np) +{ + byte *ptr; + int offset, nrecs, i; + + offset = 0x00e; + ptr = np->data + offset; + nrecs = 0; + + for (i = 0; i < np->nd.ndNRecs; ++i) + { + const byte *rec; + int reclen; + + rec = HFS_NODEREC(*np, i); + reclen = HFS_RECLEN(*np, i); + + if (HFS_RECKEYLEN(rec) > 0) + { + np->roff[nrecs++] = offset; + offset += reclen; + + if (ptr == rec) + ptr += reclen; + else + { + while (reclen--) + *ptr++ = *rec++; + } + } + } + + np->roff[nrecs] = offset; + np->nd.ndNRecs = nrecs; +} + +/* + * NAME: node->search() + * DESCRIPTION: locate a record in a node, or the record it should follow + */ +int n_search(node *np, const byte *pkey) +{ + const btree *bt = np->bt; + byte key1[HFS_MAX_KEYLEN], key2[HFS_MAX_KEYLEN]; + int i, comp = -1; + + bt->keyunpack(pkey, key2); + + for (i = np->nd.ndNRecs; i--; ) + { + const byte *rec; + + rec = HFS_NODEREC(*np, i); + + if (HFS_RECKEYLEN(rec) == 0) + continue; /* deleted record */ + + bt->keyunpack(rec, key1); + comp = bt->keycompare(key1, key2); + + if (comp <= 0) + break; + } + + np->rnum = i; + + return comp == 0; +} + +/* + * NAME: node->index() + * DESCRIPTION: create an index record from a key and node pointer + */ +void n_index(const node *np, byte *record, unsigned int *reclen) +{ + const byte *key = HFS_NODEREC(*np, 0); + + if (np->bt == &np->bt->f.vol->cat) + { + /* force the key length to be 0x25 */ + + HFS_SETKEYLEN(record, 0x25); + memset(record + 1, 0, 0x25); + memcpy(record + 1, key + 1, HFS_RECKEYLEN(key)); + } + else + memcpy(record, key, HFS_RECKEYSKIP(key)); + + d_putul(HFS_RECDATA(record), np->nnum); + + if (reclen) + *reclen = HFS_RECKEYSKIP(record) + 4; +} + +/* + * NAME: split() + * DESCRIPTION: divide a node into two and insert a record + */ +static +int split(node *left, byte *record, unsigned int *reclen) +{ + btree *bt = left->bt; + node n, *right = &n, *side = 0; + int mark, i; + + /* create a second node by cloning the first */ + + *right = *left; + + if (n_new(right) == -1) + goto fail; + + left->nd.ndFLink = right->nnum; + right->nd.ndBLink = left->nnum; + + /* divide all records evenly between the two nodes */ + + mark = (NODEUSED(*left) + 2 * left->nd.ndNRecs + *reclen + 2) >> 1; + + if (left->rnum == -1) + { + side = left; + mark -= *reclen + 2; + } + + for (i = 0; i < left->nd.ndNRecs; ++i) + { + node *np; + byte *rec; + + np = (mark > 0) ? right : left; + rec = HFS_NODEREC(*np, i); + + mark -= HFS_RECLEN(*np, i) + 2; + + HFS_SETKEYLEN(rec, 0); + + if (left->rnum == i) + { + side = (mark > 0) ? left : right; + mark -= *reclen + 2; + } + } + + compact(left); + compact(right); + + /* insert the new record and store the modified nodes */ + + ASSERT(side); + + n_search(side, record); + n_insertx(side, record, *reclen); + + if (bt_putnode(left) == -1 || + bt_putnode(right) == -1) + goto fail; + + /* create an index record in the parent for the new node */ + + n_index(right, record, reclen); + + /* update link pointers */ + + if (bt->hdr.bthLNode == left->nnum) + { + bt->hdr.bthLNode = right->nnum; + bt->flags |= HFS_BT_UPDATE_HDR; + } + + if (right->nd.ndFLink > 0) + { + node sib; + + if (bt_getnode(&sib, right->bt, right->nd.ndFLink) == -1) + goto fail; + + sib.nd.ndBLink = right->nnum; + + if (bt_putnode(&sib) == -1) + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: node->insertx() + * DESCRIPTION: insert a record into a node (which must already have room) + */ +void n_insertx(node *np, const byte *record, unsigned int reclen) +{ + int rnum, i; + byte *ptr; + + rnum = np->rnum + 1; + + /* push other records down to make room */ + + for (ptr = HFS_NODEREC(*np, np->nd.ndNRecs) + reclen; + ptr > HFS_NODEREC(*np, rnum) + reclen; --ptr) + *(ptr - 1) = *(ptr - 1 - reclen); + + ++np->nd.ndNRecs; + + for (i = np->nd.ndNRecs; i > rnum; --i) + np->roff[i] = np->roff[i - 1] + reclen; + + /* write the new record */ + + memcpy(HFS_NODEREC(*np, rnum), record, reclen); +} + +/* + * NAME: node->insert() + * DESCRIPTION: insert a new record into a node; return a record for parent + */ +int n_insert(node *np, byte *record, unsigned int *reclen) +{ + /* check for free space */ + + if (np->nd.ndNRecs >= HFS_MAX_NRECS || + *reclen + 2 > NODEFREE(*np)) + return split(np, record, reclen); + + n_insertx(np, record, *reclen); + *reclen = 0; + + return bt_putnode(np); +} + +/* + * NAME: join() + * DESCRIPTION: combine two nodes into a single node + */ +static +int join(node *left, node *right, byte *record, int *flag) +{ + int i, offset; + + /* copy records and offsets */ + + memcpy(HFS_NODEREC(*left, left->nd.ndNRecs), + HFS_NODEREC(*right, 0), NODEUSED(*right)); + + offset = left->roff[left->nd.ndNRecs] - right->roff[0]; + + for (i = 1; i <= right->nd.ndNRecs; ++i) + left->roff[++left->nd.ndNRecs] = offset + right->roff[i]; + + if (bt_putnode(left) == -1) + goto fail; + + /* eliminate node and update link pointers */ + + if (n_free(right) == -1) + goto fail; + + HFS_SETKEYLEN(record, 0); + *flag = 1; + + return 0; + +fail: + return -1; +} + +/* + * NAME: node->delete() + * DESCRIPTION: remove a record from a node + */ +int n_delete(node *np, byte *record, int *flag) +{ + byte *rec; + + rec = HFS_NODEREC(*np, np->rnum); + + HFS_SETKEYLEN(rec, 0); + compact(np); + + if (np->nd.ndNRecs == 0) + { + if (n_free(np) == -1) + goto fail; + + HFS_SETKEYLEN(record, 0); + *flag = 1; + + return 0; + } + + /* see if we can join with our left sibling */ + + if (np->nd.ndBLink > 0) + { + node left; + + if (bt_getnode(&left, np->bt, np->nd.ndBLink) == -1) + goto fail; + + if (np->nd.ndNRecs + left.nd.ndNRecs <= HFS_MAX_NRECS && + NODEUSED(*np) + 2 * np->nd.ndNRecs <= NODEFREE(left)) + return join(&left, np, record, flag); + } + + if (np->rnum == 0) + { + /* special case: first record changed; update parent record key */ + + n_index(np, record, 0); + *flag = 1; + } + + return bt_putnode(np); + +fail: + return -1; +} diff --git a/libhfs/node.h b/libhfs/node.h new file mode 100644 index 0000000..91287fd --- /dev/null +++ b/libhfs/node.h @@ -0,0 +1,34 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: node.h,v 1.7 1998/11/02 22:09:06 rob Exp $ + */ + +void n_init(node *, btree *, int, int); + +int n_new(node *); +int n_free(node *); + +int n_search(node *, const byte *); + +void n_index(const node *, byte *, unsigned int *); + +void n_insertx(node *, const byte *, unsigned int); +int n_insert(node *, byte *, unsigned int *); + +int n_delete(node *, byte *, int *); diff --git a/libhfs/os.h b/libhfs/os.h new file mode 100644 index 0000000..1d45096 --- /dev/null +++ b/libhfs/os.h @@ -0,0 +1,29 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: os.h,v 1.6 1998/09/15 19:21:05 rob Exp $ + */ + +int os_open(void **, const char *, int); +int os_close(void **); + +int os_same(void **, const char *); + +unsigned long os_seek(void **priv, long base, unsigned long offset); +unsigned long os_read(void **, void *, unsigned long); +unsigned long os_write(void **, const void *, unsigned long); diff --git a/libhfs/os/unix.c b/libhfs/os/unix.c new file mode 100755 index 0000000..8a6f1ea --- /dev/null +++ b/libhfs/os/unix.c @@ -0,0 +1,200 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: unix.c,v 1.8 1998/11/02 22:09:13 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# ifdef HAVE_FCNTL_H +# include +# else +int open(const char *, int, ...); +int fcntl(int, int, ...); +# endif + +# ifdef HAVE_UNISTD_H +# include +# else +int close(int); +off_t lseek(int, off_t, int); +ssize_t read(int, void *, size_t); +ssize_t write(int, const char *, size_t); +int stat(const char *, struct stat *); +int fstat(int, struct stat *); +# endif + +# include +# include + +# include "libhfs.h" +# include "os.h" + +/* + * NAME: os->open() + * DESCRIPTION: open and lock a new descriptor from the given path and mode + */ +int os_open(void **priv, const char *path, int mode) +{ + int fd; + struct flock lock; + + switch (mode) + { + case HFS_MODE_RDONLY: + mode = O_RDONLY; + break; + + case HFS_MODE_RDWR: + default: + mode = O_RDWR; + break; + } + + fd = open(path, mode); + if (fd == -1) + ERROR(errno, "error opening medium"); + + /* lock descriptor against concurrent access */ + + lock.l_type = (mode == O_RDONLY) ? F_RDLCK : F_WRLCK; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + if (fcntl(fd, F_SETLK, &lock) == -1 && + (errno == EACCES || errno == EAGAIN)) + ERROR(EAGAIN, "unable to obtain lock for medium"); + + *priv = (void *) fd; + + return 0; + +fail: + if (fd != -1) + close(fd); + + return -1; +} + +/* + * NAME: os->close() + * DESCRIPTION: close an open descriptor + */ +int os_close(void **priv) +{ + int fd = (int) *priv; + + *priv = (void *) -1; + + if (close(fd) == -1) + ERROR(errno, "error closing medium"); + + return 0; + +fail: + return -1; +} + +/* + * NAME: os->same() + * DESCRIPTION: return 1 if path is same as the open descriptor + */ +int os_same(void **priv, const char *path) +{ + int fd = (int) *priv; + struct stat fdev, dev; + + if (fstat(fd, &fdev) == -1 || + stat(path, &dev) == -1) + ERROR(errno, "can't get path information"); + + return fdev.st_dev == dev.st_dev && + fdev.st_ino == dev.st_ino; + +fail: + return -1; +} + +/* + * NAME: os->seek() + * DESCRIPTION: set a descriptor's seek pointer (offset in blocks) + */ +unsigned long os_seek(void **priv, long base, unsigned long offset) +{ + int fd = (int) *priv; + off_t result; + + /* offset == -1 special; seek to last block of device */ + + if (offset == (unsigned long) -1) + result = lseek(fd, 0, SEEK_END); + else + result = lseek(fd, (offset << HFS_BLOCKSZ_BITS) + base, SEEK_SET); + + if (result == -1) + ERROR(errno, "error seeking medium"); + + result -= base; + return (unsigned long) (result >> HFS_BLOCKSZ_BITS); + +fail: + return -1; +} + +/* + * NAME: os->read() + * DESCRIPTION: read blocks from an open descriptor + */ +unsigned long os_read(void **priv, void *buf, unsigned long len) +{ + int fd = (int) *priv; + ssize_t result; + + result = read(fd, buf, len << HFS_BLOCKSZ_BITS); + + if (result == -1) + ERROR(errno, "error reading from medium"); + + return (unsigned long) result >> HFS_BLOCKSZ_BITS; + +fail: + return -1; +} + +/* + * NAME: os->write() + * DESCRIPTION: write blocks to an open descriptor + */ +unsigned long os_write(void **priv, const void *buf, unsigned long len) +{ + int fd = (int) *priv; + ssize_t result; + + result = write(fd, buf, len << HFS_BLOCKSZ_BITS); + + if (result == -1) + ERROR(errno, "error writing to medium"); + + return (unsigned long) result >> HFS_BLOCKSZ_BITS; + +fail: + return -1; +} diff --git a/libhfs/record.c b/libhfs/record.c new file mode 100644 index 0000000..33097d2 --- /dev/null +++ b/libhfs/record.c @@ -0,0 +1,562 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: record.c,v 1.9 1998/11/02 22:09:07 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include + +# include "libhfs.h" +# include "record.h" +# include "data.h" + +/* + * NAME: record->packcatkey() + * DESCRIPTION: pack a catalog record key + */ +void r_packcatkey(const CatKeyRec *key, byte *pkey, unsigned int *len) +{ + const byte *start = pkey; + + d_storesb(&pkey, key->ckrKeyLen); + d_storesb(&pkey, key->ckrResrv1); + d_storeul(&pkey, key->ckrParID); + + d_storestr(&pkey, key->ckrCName, sizeof(key->ckrCName)); + + if (len) + *len = HFS_RECKEYSKIP(start); +} + +/* + * NAME: record->unpackcatkey() + * DESCRIPTION: unpack a catalog record key + */ +void r_unpackcatkey(const byte *pkey, CatKeyRec *key) +{ + d_fetchsb(&pkey, &key->ckrKeyLen); + d_fetchsb(&pkey, &key->ckrResrv1); + d_fetchul(&pkey, &key->ckrParID); + + d_fetchstr(&pkey, key->ckrCName, sizeof(key->ckrCName)); +} + +/* + * NAME: record->packextkey() + * DESCRIPTION: pack an extents record key + */ +void r_packextkey(const ExtKeyRec *key, byte *pkey, unsigned int *len) +{ + const byte *start = pkey; + + d_storesb(&pkey, key->xkrKeyLen); + d_storesb(&pkey, key->xkrFkType); + d_storeul(&pkey, key->xkrFNum); + d_storeuw(&pkey, key->xkrFABN); + + if (len) + *len = HFS_RECKEYSKIP(start); +} + +/* + * NAME: record->unpackextkey() + * DESCRIPTION: unpack an extents record key + */ +void r_unpackextkey(const byte *pkey, ExtKeyRec *key) +{ + d_fetchsb(&pkey, &key->xkrKeyLen); + d_fetchsb(&pkey, &key->xkrFkType); + d_fetchul(&pkey, &key->xkrFNum); + d_fetchuw(&pkey, &key->xkrFABN); +} + +/* + * NAME: record->comparecatkeys() + * DESCRIPTION: compare two (packed) catalog record keys + */ +int r_comparecatkeys(const CatKeyRec *key1, const CatKeyRec *key2) +{ + int diff; + + diff = key1->ckrParID - key2->ckrParID; + if (diff) + return diff; + + return d_relstring(key1->ckrCName, key2->ckrCName); +} + +/* + * NAME: record->compareextkeys() + * DESCRIPTION: compare two (packed) extents record keys + */ +int r_compareextkeys(const ExtKeyRec *key1, const ExtKeyRec *key2) +{ + int diff; + + diff = key1->xkrFNum - key2->xkrFNum; + if (diff) + return diff; + + diff = (unsigned char) key1->xkrFkType - + (unsigned char) key2->xkrFkType; + if (diff) + return diff; + + return key1->xkrFABN - key2->xkrFABN; +} + +/* + * NAME: record->packcatdata() + * DESCRIPTION: pack catalog record data + */ +void r_packcatdata(const CatDataRec *data, byte *pdata, unsigned int *len) +{ + const byte *start = pdata; + int i; + + d_storesb(&pdata, data->cdrType); + d_storesb(&pdata, data->cdrResrv2); + + switch (data->cdrType) + { + case cdrDirRec: + d_storesw(&pdata, data->u.dir.dirFlags); + d_storeuw(&pdata, data->u.dir.dirVal); + d_storeul(&pdata, data->u.dir.dirDirID); + d_storesl(&pdata, data->u.dir.dirCrDat); + d_storesl(&pdata, data->u.dir.dirMdDat); + d_storesl(&pdata, data->u.dir.dirBkDat); + + d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.top); + d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.left); + d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.bottom); + d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.right); + d_storesw(&pdata, data->u.dir.dirUsrInfo.frFlags); + d_storesw(&pdata, data->u.dir.dirUsrInfo.frLocation.v); + d_storesw(&pdata, data->u.dir.dirUsrInfo.frLocation.h); + d_storesw(&pdata, data->u.dir.dirUsrInfo.frView); + + d_storesw(&pdata, data->u.dir.dirFndrInfo.frScroll.v); + d_storesw(&pdata, data->u.dir.dirFndrInfo.frScroll.h); + d_storesl(&pdata, data->u.dir.dirFndrInfo.frOpenChain); + d_storesw(&pdata, data->u.dir.dirFndrInfo.frUnused); + d_storesw(&pdata, data->u.dir.dirFndrInfo.frComment); + d_storesl(&pdata, data->u.dir.dirFndrInfo.frPutAway); + + for (i = 0; i < 4; ++i) + d_storesl(&pdata, data->u.dir.dirResrv[i]); + + break; + + case cdrFilRec: + d_storesb(&pdata, data->u.fil.filFlags); + d_storesb(&pdata, data->u.fil.filTyp); + + d_storesl(&pdata, data->u.fil.filUsrWds.fdType); + d_storesl(&pdata, data->u.fil.filUsrWds.fdCreator); + d_storesw(&pdata, data->u.fil.filUsrWds.fdFlags); + d_storesw(&pdata, data->u.fil.filUsrWds.fdLocation.v); + d_storesw(&pdata, data->u.fil.filUsrWds.fdLocation.h); + d_storesw(&pdata, data->u.fil.filUsrWds.fdFldr); + + d_storeul(&pdata, data->u.fil.filFlNum); + + d_storeuw(&pdata, data->u.fil.filStBlk); + d_storeul(&pdata, data->u.fil.filLgLen); + d_storeul(&pdata, data->u.fil.filPyLen); + + d_storeuw(&pdata, data->u.fil.filRStBlk); + d_storeul(&pdata, data->u.fil.filRLgLen); + d_storeul(&pdata, data->u.fil.filRPyLen); + + d_storesl(&pdata, data->u.fil.filCrDat); + d_storesl(&pdata, data->u.fil.filMdDat); + d_storesl(&pdata, data->u.fil.filBkDat); + + d_storesw(&pdata, data->u.fil.filFndrInfo.fdIconID); + for (i = 0; i < 4; ++i) + d_storesw(&pdata, data->u.fil.filFndrInfo.fdUnused[i]); + d_storesw(&pdata, data->u.fil.filFndrInfo.fdComment); + d_storesl(&pdata, data->u.fil.filFndrInfo.fdPutAway); + + d_storeuw(&pdata, data->u.fil.filClpSize); + + for (i = 0; i < 3; ++i) + { + d_storeuw(&pdata, data->u.fil.filExtRec[i].xdrStABN); + d_storeuw(&pdata, data->u.fil.filExtRec[i].xdrNumABlks); + } + + for (i = 0; i < 3; ++i) + { + d_storeuw(&pdata, data->u.fil.filRExtRec[i].xdrStABN); + d_storeuw(&pdata, data->u.fil.filRExtRec[i].xdrNumABlks); + } + + d_storesl(&pdata, data->u.fil.filResrv); + + break; + + case cdrThdRec: + for (i = 0; i < 2; ++i) + d_storesl(&pdata, data->u.dthd.thdResrv[i]); + + d_storeul(&pdata, data->u.dthd.thdParID); + + d_storestr(&pdata, data->u.dthd.thdCName, + sizeof(data->u.dthd.thdCName)); + + break; + + case cdrFThdRec: + for (i = 0; i < 2; ++i) + d_storesl(&pdata, data->u.fthd.fthdResrv[i]); + + d_storeul(&pdata, data->u.fthd.fthdParID); + + d_storestr(&pdata, data->u.fthd.fthdCName, + sizeof(data->u.fthd.fthdCName)); + + break; + + default: + ASSERT(0); + } + + if (len) + *len += pdata - start; +} + +/* + * NAME: record->unpackcatdata() + * DESCRIPTION: unpack catalog record data + */ +void r_unpackcatdata(const byte *pdata, CatDataRec *data) +{ + int i; + + d_fetchsb(&pdata, &data->cdrType); + d_fetchsb(&pdata, &data->cdrResrv2); + + switch (data->cdrType) + { + case cdrDirRec: + d_fetchsw(&pdata, &data->u.dir.dirFlags); + d_fetchuw(&pdata, &data->u.dir.dirVal); + d_fetchul(&pdata, &data->u.dir.dirDirID); + d_fetchsl(&pdata, &data->u.dir.dirCrDat); + d_fetchsl(&pdata, &data->u.dir.dirMdDat); + d_fetchsl(&pdata, &data->u.dir.dirBkDat); + + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.top); + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.left); + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.bottom); + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.right); + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frFlags); + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frLocation.v); + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frLocation.h); + d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frView); + + d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frScroll.v); + d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frScroll.h); + d_fetchsl(&pdata, &data->u.dir.dirFndrInfo.frOpenChain); + d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frUnused); + d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frComment); + d_fetchsl(&pdata, &data->u.dir.dirFndrInfo.frPutAway); + + for (i = 0; i < 4; ++i) + d_fetchsl(&pdata, &data->u.dir.dirResrv[i]); + + break; + + case cdrFilRec: + d_fetchsb(&pdata, &data->u.fil.filFlags); + d_fetchsb(&pdata, &data->u.fil.filTyp); + + d_fetchsl(&pdata, &data->u.fil.filUsrWds.fdType); + d_fetchsl(&pdata, &data->u.fil.filUsrWds.fdCreator); + d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdFlags); + d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdLocation.v); + d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdLocation.h); + d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdFldr); + + d_fetchul(&pdata, &data->u.fil.filFlNum); + + d_fetchuw(&pdata, &data->u.fil.filStBlk); + d_fetchul(&pdata, &data->u.fil.filLgLen); + d_fetchul(&pdata, &data->u.fil.filPyLen); + + d_fetchuw(&pdata, &data->u.fil.filRStBlk); + d_fetchul(&pdata, &data->u.fil.filRLgLen); + d_fetchul(&pdata, &data->u.fil.filRPyLen); + + d_fetchsl(&pdata, &data->u.fil.filCrDat); + d_fetchsl(&pdata, &data->u.fil.filMdDat); + d_fetchsl(&pdata, &data->u.fil.filBkDat); + + d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdIconID); + for (i = 0; i < 4; ++i) + d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdUnused[i]); + d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdComment); + d_fetchsl(&pdata, &data->u.fil.filFndrInfo.fdPutAway); + + d_fetchuw(&pdata, &data->u.fil.filClpSize); + + for (i = 0; i < 3; ++i) + { + d_fetchuw(&pdata, &data->u.fil.filExtRec[i].xdrStABN); + d_fetchuw(&pdata, &data->u.fil.filExtRec[i].xdrNumABlks); + } + + for (i = 0; i < 3; ++i) + { + d_fetchuw(&pdata, &data->u.fil.filRExtRec[i].xdrStABN); + d_fetchuw(&pdata, &data->u.fil.filRExtRec[i].xdrNumABlks); + } + + d_fetchsl(&pdata, &data->u.fil.filResrv); + + break; + + case cdrThdRec: + for (i = 0; i < 2; ++i) + d_fetchsl(&pdata, &data->u.dthd.thdResrv[i]); + + d_fetchul(&pdata, &data->u.dthd.thdParID); + + d_fetchstr(&pdata, data->u.dthd.thdCName, + sizeof(data->u.dthd.thdCName)); + + break; + + case cdrFThdRec: + for (i = 0; i < 2; ++i) + d_fetchsl(&pdata, &data->u.fthd.fthdResrv[i]); + + d_fetchul(&pdata, &data->u.fthd.fthdParID); + + d_fetchstr(&pdata, data->u.fthd.fthdCName, + sizeof(data->u.fthd.fthdCName)); + + break; + + default: + ASSERT(0); + } +} + +/* + * NAME: record->packextdata() + * DESCRIPTION: pack extent record data + */ +void r_packextdata(const ExtDataRec *data, byte *pdata, unsigned int *len) +{ + const byte *start = pdata; + int i; + + for (i = 0; i < 3; ++i) + { + d_storeuw(&pdata, (*data)[i].xdrStABN); + d_storeuw(&pdata, (*data)[i].xdrNumABlks); + } + + if (len) + *len += pdata - start; +} + +/* + * NAME: record->unpackextdata() + * DESCRIPTION: unpack extent record data + */ +void r_unpackextdata(const byte *pdata, ExtDataRec *data) +{ + int i; + + for (i = 0; i < 3; ++i) + { + d_fetchuw(&pdata, &(*data)[i].xdrStABN); + d_fetchuw(&pdata, &(*data)[i].xdrNumABlks); + } +} + +/* + * NAME: record->makecatkey() + * DESCRIPTION: construct a catalog record key + */ +void r_makecatkey(CatKeyRec *key, unsigned long parid, const char *name) +{ + int len; + + len = strlen(name) + 1; + + key->ckrKeyLen = 0x05 + len + (len & 1); + key->ckrResrv1 = 0; + key->ckrParID = parid; + + strcpy(key->ckrCName, name); +} + +/* + * NAME: record->makeextkey() + * DESCRIPTION: construct an extents record key + */ +void r_makeextkey(ExtKeyRec *key, + int fork, unsigned long fnum, unsigned int fabn) +{ + key->xkrKeyLen = 0x07; + key->xkrFkType = fork; + key->xkrFNum = fnum; + key->xkrFABN = fabn; +} + +/* + * NAME: record->packcatrec() + * DESCRIPTION: create a packed catalog record + */ +void r_packcatrec(const CatKeyRec *key, const CatDataRec *data, + byte *precord, unsigned int *len) +{ + r_packcatkey(key, precord, len); + r_packcatdata(data, HFS_RECDATA(precord), len); +} + +/* + * NAME: record->packextrec() + * DESCRIPTION: create a packed extents record + */ +void r_packextrec(const ExtKeyRec *key, const ExtDataRec *data, + byte *precord, unsigned int *len) +{ + r_packextkey(key, precord, len); + r_packextdata(data, HFS_RECDATA(precord), len); +} + +/* + * NAME: record->packdirent() + * DESCRIPTION: make changes to a catalog record + */ +void r_packdirent(CatDataRec *data, const hfsdirent *ent) +{ + switch (data->cdrType) + { + case cdrDirRec: + data->u.dir.dirCrDat = d_mtime(ent->crdate); + data->u.dir.dirMdDat = d_mtime(ent->mddate); + data->u.dir.dirBkDat = d_mtime(ent->bkdate); + + data->u.dir.dirUsrInfo.frFlags = ent->fdflags; + data->u.dir.dirFndrInfo.frComment = ent->fdcomment; + data->u.dir.dirUsrInfo.frLocation.v = ent->fdlocation.v; + data->u.dir.dirUsrInfo.frLocation.h = ent->fdlocation.h; + + data->u.dir.dirUsrInfo.frRect.top = ent->u.dir.rect.top; + data->u.dir.dirUsrInfo.frRect.left = ent->u.dir.rect.left; + data->u.dir.dirUsrInfo.frRect.bottom = ent->u.dir.rect.bottom; + data->u.dir.dirUsrInfo.frRect.right = ent->u.dir.rect.right; + + break; + + case cdrFilRec: + if (ent->flags & HFS_ISLOCKED) + data->u.fil.filFlags |= (1 << 0); + else + data->u.fil.filFlags &= ~(1 << 0); + + data->u.fil.filCrDat = d_mtime(ent->crdate); + data->u.fil.filMdDat = d_mtime(ent->mddate); + data->u.fil.filBkDat = d_mtime(ent->bkdate); + + data->u.fil.filUsrWds.fdFlags = ent->fdflags; + data->u.fil.filFndrInfo.fdComment = ent->fdcomment; + + data->u.fil.filUsrWds.fdLocation.v = ent->fdlocation.v; + data->u.fil.filUsrWds.fdLocation.h = ent->fdlocation.h; + + data->u.fil.filUsrWds.fdType = + d_getsl((const unsigned char *) ent->u.file.type); + data->u.fil.filUsrWds.fdCreator = + d_getsl((const unsigned char *) ent->u.file.creator); + + break; + } +} + +/* + * NAME: record->unpackdirent() + * DESCRIPTION: unpack catalog information into hfsdirent structure + */ +void r_unpackdirent(unsigned long parid, const char *name, + const CatDataRec *data, hfsdirent *ent) +{ + strcpy(ent->name, name); + ent->parid = parid; + + switch (data->cdrType) + { + case cdrDirRec: + ent->flags = HFS_ISDIR; + ent->cnid = data->u.dir.dirDirID; + + ent->crdate = d_ltime(data->u.dir.dirCrDat); + ent->mddate = d_ltime(data->u.dir.dirMdDat); + ent->bkdate = d_ltime(data->u.dir.dirBkDat); + + ent->fdflags = data->u.dir.dirUsrInfo.frFlags; + ent->fdcomment = data->u.dir.dirFndrInfo.frComment; + ent->fdlocation.v = data->u.dir.dirUsrInfo.frLocation.v; + ent->fdlocation.h = data->u.dir.dirUsrInfo.frLocation.h; + + ent->u.dir.valence = data->u.dir.dirVal; + + ent->u.dir.rect.top = data->u.dir.dirUsrInfo.frRect.top; + ent->u.dir.rect.left = data->u.dir.dirUsrInfo.frRect.left; + ent->u.dir.rect.bottom = data->u.dir.dirUsrInfo.frRect.bottom; + ent->u.dir.rect.right = data->u.dir.dirUsrInfo.frRect.right; + + break; + + case cdrFilRec: + ent->flags = (data->u.fil.filFlags & (1 << 0)) ? HFS_ISLOCKED : 0; + ent->cnid = data->u.fil.filFlNum; + + ent->crdate = d_ltime(data->u.fil.filCrDat); + ent->mddate = d_ltime(data->u.fil.filMdDat); + ent->bkdate = d_ltime(data->u.fil.filBkDat); + + ent->fdflags = data->u.fil.filUsrWds.fdFlags; + ent->fdcomment = data->u.fil.filFndrInfo.fdComment; + ent->fdlocation.v = data->u.fil.filUsrWds.fdLocation.v; + ent->fdlocation.h = data->u.fil.filUsrWds.fdLocation.h; + + ent->u.file.dsize = data->u.fil.filLgLen; + ent->u.file.rsize = data->u.fil.filRLgLen; + + d_putsl((unsigned char *) ent->u.file.type, + data->u.fil.filUsrWds.fdType); + d_putsl((unsigned char *) ent->u.file.creator, + data->u.fil.filUsrWds.fdCreator); + + ent->u.file.type[4] = ent->u.file.creator[4] = 0; + + break; + } +} diff --git a/libhfs/record.h b/libhfs/record.h new file mode 100644 index 0000000..4959dc8 --- /dev/null +++ b/libhfs/record.h @@ -0,0 +1,47 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: record.h,v 1.7 1998/11/02 22:09:08 rob Exp $ + */ + +void r_packcatkey(const CatKeyRec *, byte *, unsigned int *); +void r_unpackcatkey(const byte *, CatKeyRec *); + +void r_packextkey(const ExtKeyRec *, byte *, unsigned int *); +void r_unpackextkey(const byte *, ExtKeyRec *); + +int r_comparecatkeys(const CatKeyRec *, const CatKeyRec *); +int r_compareextkeys(const ExtKeyRec *, const ExtKeyRec *); + +void r_packcatdata(const CatDataRec *, byte *, unsigned int *); +void r_unpackcatdata(const byte *, CatDataRec *); + +void r_packextdata(const ExtDataRec *, byte *, unsigned int *); +void r_unpackextdata(const byte *, ExtDataRec *); + +void r_makecatkey(CatKeyRec *, unsigned long, const char *); +void r_makeextkey(ExtKeyRec *, int, unsigned long, unsigned int); + +void r_packcatrec(const CatKeyRec *, const CatDataRec *, + byte *, unsigned int *); +void r_packextrec(const ExtKeyRec *, const ExtDataRec *, + byte *, unsigned int *); + +void r_packdirent(CatDataRec *, const hfsdirent *); +void r_unpackdirent(unsigned long, const char *, + const CatDataRec *, hfsdirent *); diff --git a/libhfs/version.c b/libhfs/version.c new file mode 100644 index 0000000..20fd093 --- /dev/null +++ b/libhfs/version.c @@ -0,0 +1,29 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: version.c,v 1.11 1998/11/02 22:09:09 rob Exp $ + */ + +# include "version.h" + +const char libhfs_rcsid[] = + "$Id: version.c,v 1.11 1998/11/02 22:09:09 rob Exp $"; + +const char libhfs_version[] = "libhfs version 3.2.6"; +const char libhfs_copyright[] = "Copyright (C) 1996-1998 Robert Leslie"; +const char libhfs_author[] = "Robert Leslie "; diff --git a/libhfs/version.h b/libhfs/version.h new file mode 100644 index 0000000..ae6a1d3 --- /dev/null +++ b/libhfs/version.h @@ -0,0 +1,26 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: version.h,v 1.6 1998/09/18 22:56:38 rob Exp $ + */ + +extern const char libhfs_rcsid[]; + +extern const char libhfs_version[]; +extern const char libhfs_copyright[]; +extern const char libhfs_author[]; diff --git a/libhfs/volume.c b/libhfs/volume.c new file mode 100644 index 0000000..b252152 --- /dev/null +++ b/libhfs/volume.c @@ -0,0 +1,1203 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: volume.c,v 1.12 1998/11/02 22:09:10 rob Exp $ + */ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include +# include + +# include "libhfs.h" +# include "volume.h" +# include "data.h" +# include "block.h" +# include "low.h" +# include "medium.h" +# include "file.h" +# include "btree.h" +# include "record.h" +# include "os.h" + +/* + * NAME: vol->init() + * DESCRIPTION: initialize volume structure + */ +void v_init(hfsvol *vol, int flags) +{ + btree *ext = &vol->ext; + btree *cat = &vol->cat; + + vol->priv = 0; + vol->flags = flags & HFS_VOL_OPT_MASK; + vol->base = (flags & HFS_OPT_DC42HEADER)? 84 : 0; + + vol->pnum = -1; + vol->vstart = 0; + vol->vlen = 0; + vol->lpa = 0; + + vol->cache = 0; + + vol->vbm = 0; + vol->vbmsz = 0; + + f_init(&ext->f, vol, HFS_CNID_EXT, "extents overflow"); + + ext->map = 0; + ext->mapsz = 0; + ext->flags = 0; + + ext->keyunpack = (keyunpackfunc) r_unpackextkey; + ext->keycompare = (keycomparefunc) r_compareextkeys; + + f_init(&cat->f, vol, HFS_CNID_CAT, "catalog"); + + cat->map = 0; + cat->mapsz = 0; + cat->flags = 0; + + cat->keyunpack = (keyunpackfunc) r_unpackcatkey; + cat->keycompare = (keycomparefunc) r_comparecatkeys; + + vol->cwd = HFS_CNID_ROOTDIR; + + vol->refs = 0; + vol->files = 0; + vol->dirs = 0; + + vol->prev = 0; + vol->next = 0; +} + +/* + * NAME: vol->open() + * DESCRIPTION: open volume source and lock against concurrent updates + */ +int v_open(hfsvol *vol, const char *path, int mode) +{ + if (vol->flags & HFS_VOL_OPEN) + ERROR(EINVAL, "volume already open"); + + if (os_open(&vol->priv, path, mode) == -1) + goto fail; + + vol->flags |= HFS_VOL_OPEN; + + /* initialize volume block cache (OK to fail) */ + + if (! (vol->flags & HFS_OPT_NOCACHE) && + b_init(vol) != -1) + vol->flags |= HFS_VOL_USINGCACHE; + + return 0; + +fail: + return -1; +} + +/* + * NAME: flushvol() + * DESCRIPTION: flush all pending changes (B*-tree, MDB, VBM) to volume + */ +static +int flushvol(hfsvol *vol, int umount) +{ + if (vol->flags & HFS_VOL_READONLY) + goto done; + + if ((vol->ext.flags & HFS_BT_UPDATE_HDR) && + bt_writehdr(&vol->ext) == -1) + goto fail; + + if ((vol->cat.flags & HFS_BT_UPDATE_HDR) && + bt_writehdr(&vol->cat) == -1) + goto fail; + + if ((vol->flags & HFS_VOL_UPDATE_VBM) && + v_writevbm(vol) == -1) + goto fail; + + if (umount && ! (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED)) + { + vol->mdb.drAtrb |= HFS_ATRB_UMOUNTED; + vol->flags |= HFS_VOL_UPDATE_MDB; + } + + if ((vol->flags & (HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_ALTMDB)) && + v_writemdb(vol) == -1) + goto fail; + +done: + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->flush() + * DESCRIPTION: commit all pending changes to volume device + */ +int v_flush(hfsvol *vol) +{ + if (flushvol(vol, 0) == -1) + goto fail; + + if ((vol->flags & HFS_VOL_USINGCACHE) && + b_flush(vol) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->close() + * DESCRIPTION: close access path to volume source + */ +int v_close(hfsvol *vol) +{ + int result = 0; + + if (! (vol->flags & HFS_VOL_OPEN)) + goto done; + + if ((vol->flags & HFS_VOL_MOUNTED) && + flushvol(vol, 1) == -1) + result = -1; + + if ((vol->flags & HFS_VOL_USINGCACHE) && + b_finish(vol) == -1) + result = -1; + + if (os_close(&vol->priv) == -1) + result = -1; + + vol->flags &= ~(HFS_VOL_OPEN | HFS_VOL_MOUNTED | HFS_VOL_USINGCACHE); + + /* free dynamically allocated structures */ + + FREE(vol->vbm); + + vol->vbm = 0; + vol->vbmsz = 0; + + FREE(vol->ext.map); + FREE(vol->cat.map); + + vol->ext.map = 0; + vol->cat.map = 0; + +done: + return result; +} + +/* + * NAME: vol->same() + * DESCRIPTION: return 1 iff path is same as open volume + */ +int v_same(hfsvol *vol, const char *path) +{ + return os_same(&vol->priv, path); +} + +/* + * NAME: vol->geometry() + * DESCRIPTION: determine volume location and size (possibly in a partition) + */ +int v_geometry(hfsvol *vol, int pnum) +{ + Partition map; + unsigned long bnum = 0; + int found; + + vol->pnum = pnum; + + if (pnum == 0) + { + vol->vstart = 0; + vol->vlen = b_size(vol); + + if (vol->vlen == 0) + goto fail; + } + else + { + while (pnum--) + { + found = m_findpmentry(vol, "Apple_HFS", &map, &bnum); + if (found == -1 || ! found) + goto fail; + } + + vol->vstart = map.pmPyPartStart; + vol->vlen = map.pmPartBlkCnt; + + if (map.pmDataCnt) + { + if ((unsigned long) map.pmLgDataStart + + (unsigned long) map.pmDataCnt > vol->vlen) + ERROR(EINVAL, "partition data overflows partition"); + + vol->vstart += (unsigned long) map.pmLgDataStart; + vol->vlen = map.pmDataCnt; + } + + if (vol->vlen == 0) + ERROR(EINVAL, "volume partition is empty"); + } + + /*if (vol->vlen < 800 * (1024 >> HFS_BLOCKSZ_BITS)) + ERROR(EINVAL, "volume is smaller than 800K");*/ + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->readmdb() + * DESCRIPTION: load Master Directory Block into memory + */ +int v_readmdb(hfsvol *vol) +{ + if (l_getmdb(vol, &vol->mdb, 0) == -1) + goto fail; + + if (vol->mdb.drSigWord != HFS_SIGWORD) + { + if (vol->mdb.drSigWord == HFS_SIGWORD_MFS) + ERROR(EINVAL, "MFS volume format not supported"); + else + ERROR(EINVAL, "not a Macintosh HFS volume"); + } + + if (vol->mdb.drAlBlkSiz % HFS_BLOCKSZ != 0) + ERROR(EINVAL, "bad volume allocation block size"); + + vol->lpa = vol->mdb.drAlBlkSiz >> HFS_BLOCKSZ_BITS; + + /* extents pseudo-file structs */ + + vol->ext.f.cat.u.fil.filStBlk = vol->mdb.drXTExtRec[0].xdrStABN; + vol->ext.f.cat.u.fil.filLgLen = vol->mdb.drXTFlSize; + vol->ext.f.cat.u.fil.filPyLen = vol->mdb.drXTFlSize; + + vol->ext.f.cat.u.fil.filCrDat = vol->mdb.drCrDate; + vol->ext.f.cat.u.fil.filMdDat = vol->mdb.drLsMod; + + memcpy(&vol->ext.f.cat.u.fil.filExtRec, + &vol->mdb.drXTExtRec, sizeof(ExtDataRec)); + + f_selectfork(&vol->ext.f, fkData); + + /* catalog pseudo-file structs */ + + vol->cat.f.cat.u.fil.filStBlk = vol->mdb.drCTExtRec[0].xdrStABN; + vol->cat.f.cat.u.fil.filLgLen = vol->mdb.drCTFlSize; + vol->cat.f.cat.u.fil.filPyLen = vol->mdb.drCTFlSize; + + vol->cat.f.cat.u.fil.filCrDat = vol->mdb.drCrDate; + vol->cat.f.cat.u.fil.filMdDat = vol->mdb.drLsMod; + + memcpy(&vol->cat.f.cat.u.fil.filExtRec, + &vol->mdb.drCTExtRec, sizeof(ExtDataRec)); + + f_selectfork(&vol->cat.f, fkData); + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->writemdb() + * DESCRIPTION: flush Master Directory Block to medium + */ +int v_writemdb(hfsvol *vol) +{ + vol->mdb.drLsMod = d_mtime(time(0)); + + vol->mdb.drXTFlSize = vol->ext.f.cat.u.fil.filPyLen; + memcpy(&vol->mdb.drXTExtRec, + &vol->ext.f.cat.u.fil.filExtRec, sizeof(ExtDataRec)); + + vol->mdb.drCTFlSize = vol->cat.f.cat.u.fil.filPyLen; + memcpy(&vol->mdb.drCTExtRec, + &vol->cat.f.cat.u.fil.filExtRec, sizeof(ExtDataRec)); + + if (l_putmdb(vol, &vol->mdb, vol->flags & HFS_VOL_UPDATE_ALTMDB) == -1) + goto fail; + + vol->flags &= ~(HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_ALTMDB); + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->readvbm() + * DESCRIPTION: read volume bitmap into memory + */ +int v_readvbm(hfsvol *vol) +{ + unsigned int vbmst = vol->mdb.drVBMSt; + unsigned int vbmsz = (vol->mdb.drNmAlBlks + 0x0fff) >> 12; + block *bp; + + ASSERT(vol->vbm == 0); + + if (vol->mdb.drAlBlSt - vbmst < vbmsz) + ERROR(EIO, "volume bitmap collides with volume data"); + + vol->vbm = ALLOC(block, vbmsz); + if (vol->vbm == 0) + ERROR(ENOMEM, 0); + + vol->vbmsz = vbmsz; + + for (bp = vol->vbm; vbmsz--; ++bp) + { + if (b_readlb(vol, vbmst++, bp) == -1) + goto fail; + } + + return 0; + +fail: + FREE(vol->vbm); + + vol->vbm = 0; + vol->vbmsz = 0; + + return -1; +} + +/* + * NAME: vol->writevbm() + * DESCRIPTION: flush volume bitmap to medium + */ +int v_writevbm(hfsvol *vol) +{ + unsigned int vbmst = vol->mdb.drVBMSt; + unsigned int vbmsz = vol->vbmsz; + const block *bp; + + for (bp = vol->vbm; vbmsz--; ++bp) + { + if (b_writelb(vol, vbmst++, bp) == -1) + goto fail; + } + + vol->flags &= ~HFS_VOL_UPDATE_VBM; + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->mount() + * DESCRIPTION: load volume information into memory + */ +int v_mount(hfsvol *vol) +{ + /* read the MDB, volume bitmap, and extents/catalog B*-tree headers */ + + if (v_readmdb(vol) == -1 || + v_readvbm(vol) == -1 || + bt_readhdr(&vol->ext) == -1 || + bt_readhdr(&vol->cat) == -1) + goto fail; + + if (! (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED) && + v_scavenge(vol) == -1) + goto fail; + + if (vol->mdb.drAtrb & HFS_ATRB_SLOCKED) + vol->flags |= HFS_VOL_READONLY; + else if (vol->flags & HFS_VOL_READONLY) + vol->mdb.drAtrb |= HFS_ATRB_HLOCKED; + else + vol->mdb.drAtrb &= ~HFS_ATRB_HLOCKED; + + vol->flags |= HFS_VOL_MOUNTED; + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->dirty() + * DESCRIPTION: ensure the volume is marked "in use" before we make changes + */ +int v_dirty(hfsvol *vol) +{ + if (vol->mdb.drAtrb & HFS_ATRB_UMOUNTED) + { + vol->mdb.drAtrb &= ~HFS_ATRB_UMOUNTED; + ++vol->mdb.drWrCnt; + + if (v_writemdb(vol) == -1) + goto fail; + + if ((vol->flags & HFS_VOL_USINGCACHE) && + b_flush(vol) == -1) + goto fail; + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->catsearch() + * DESCRIPTION: search catalog tree + */ +int v_catsearch(hfsvol *vol, unsigned long parid, const char *name, + CatDataRec *data, char *cname, node *np) +{ + CatKeyRec key; + byte pkey[HFS_CATKEYLEN]; + const byte *ptr; + node n; + int found; + + if (np == 0) + np = &n; + + r_makecatkey(&key, parid, name); + r_packcatkey(&key, pkey, 0); + + found = bt_search(&vol->cat, pkey, np); + if (found <= 0) + return found; + + ptr = HFS_NODEREC(*np, np->rnum); + + if (cname) + { + r_unpackcatkey(ptr, &key); + strcpy(cname, key.ckrCName); + } + + if (data) + r_unpackcatdata(HFS_RECDATA(ptr), data); + + return 1; +} + +/* + * NAME: vol->extsearch() + * DESCRIPTION: search extents tree + */ +int v_extsearch(hfsfile *file, unsigned int fabn, + ExtDataRec *data, node *np) +{ + ExtKeyRec key; + ExtDataRec extsave; + unsigned int fabnsave; + byte pkey[HFS_EXTKEYLEN]; + const byte *ptr; + node n; + int found; + + if (np == 0) + np = &n; + + r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, fabn); + r_packextkey(&key, pkey, 0); + + /* in case bt_search() clobbers these */ + + memcpy(&extsave, &file->ext, sizeof(ExtDataRec)); + fabnsave = file->fabn; + + found = bt_search(&file->vol->ext, pkey, np); + + memcpy(&file->ext, &extsave, sizeof(ExtDataRec)); + file->fabn = fabnsave; + + if (found <= 0) + return found; + + if (data) + { + ptr = HFS_NODEREC(*np, np->rnum); + r_unpackextdata(HFS_RECDATA(ptr), data); + } + + return 1; +} + +/* + * NAME: vol->getthread() + * DESCRIPTION: retrieve catalog thread information for a file or directory + */ +int v_getthread(hfsvol *vol, unsigned long id, + CatDataRec *thread, node *np, int type) +{ + CatDataRec rec; + int found; + + if (thread == 0) + thread = &rec; + + found = v_catsearch(vol, id, "", thread, 0, np); + if (found == 1 && thread->cdrType != type) + ERROR(EIO, "bad thread record"); + + return found; + +fail: + return -1; +} + +/* + * NAME: vol->putcatrec() + * DESCRIPTION: store catalog information + */ +int v_putcatrec(const CatDataRec *data, node *np) +{ + byte pdata[HFS_CATDATALEN], *ptr; + unsigned int len = 0; + + r_packcatdata(data, pdata, &len); + + ptr = HFS_NODEREC(*np, np->rnum); + memcpy(HFS_RECDATA(ptr), pdata, len); + + return bt_putnode(np); +} + +/* + * NAME: vol->putextrec() + * DESCRIPTION: store extent information + */ +int v_putextrec(const ExtDataRec *data, node *np) +{ + byte pdata[HFS_EXTDATALEN], *ptr; + unsigned int len = 0; + + r_packextdata(data, pdata, &len); + + ptr = HFS_NODEREC(*np, np->rnum); + memcpy(HFS_RECDATA(ptr), pdata, len); + + return bt_putnode(np); +} + +/* + * NAME: vol->allocblocks() + * DESCRIPTION: allocate a contiguous range of blocks + */ +int v_allocblocks(hfsvol *vol, ExtDescriptor *blocks) +{ + unsigned int request, found, foundat, start, end; + register unsigned int pt; + block *vbm; + int wrap = 0; + + if (vol->mdb.drFreeBks == 0) + ERROR(ENOSPC, "volume full"); + + request = blocks->xdrNumABlks; + found = 0; + foundat = 0; + start = vol->mdb.drAllocPtr; + end = vol->mdb.drNmAlBlks; + vbm = vol->vbm; + + ASSERT(request > 0); + + /* backtrack the start pointer to recover unused space */ + + if (! BMTST(vbm, start)) + { + while (start > 0 && ! BMTST(vbm, start - 1)) + --start; + } + + /* find largest unused block which satisfies request */ + + pt = start; + + while (1) + { + unsigned int mark; + + /* skip blocks in use */ + + while (pt < end && BMTST(vbm, pt)) + ++pt; + + if (wrap && pt >= start) + break; + + /* count blocks not in use */ + + mark = pt; + while (pt < end && pt - mark < request && ! BMTST(vbm, pt)) + ++pt; + + if (pt - mark > found) + { + found = pt - mark; + foundat = mark; + } + + if (wrap && pt >= start) + break; + + if (pt == end) + pt = 0, wrap = 1; + + if (found == request) + break; + } + + if (found == 0 || found > vol->mdb.drFreeBks) + ERROR(EIO, "bad volume bitmap or free block count"); + + blocks->xdrStABN = foundat; + blocks->xdrNumABlks = found; + + if (v_dirty(vol) == -1) + goto fail; + + vol->mdb.drAllocPtr = pt; + vol->mdb.drFreeBks -= found; + + for (pt = foundat; pt < foundat + found; ++pt) + BMSET(vbm, pt); + + vol->flags |= HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_VBM; + + if (vol->flags & HFS_OPT_ZERO) + { + block b; + unsigned int i; + + memset(&b, 0, sizeof(b)); + + for (pt = foundat; pt < foundat + found; ++pt) + { + for (i = 0; i < vol->lpa; ++i) + b_writeab(vol, pt, i, &b); + } + } + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->freeblocks() + * DESCRIPTION: deallocate a contiguous range of blocks + */ +int v_freeblocks(hfsvol *vol, const ExtDescriptor *blocks) +{ + unsigned int start, len, pt; + block *vbm; + + start = blocks->xdrStABN; + len = blocks->xdrNumABlks; + vbm = vol->vbm; + + if (v_dirty(vol) == -1) + goto fail; + + vol->mdb.drFreeBks += len; + + for (pt = start; pt < start + len; ++pt) + BMCLR(vbm, pt); + + vol->flags |= HFS_VOL_UPDATE_MDB | HFS_VOL_UPDATE_VBM; + + return 0; + +fail: + return -1; +} + +/* + * NAME: vol->resolve() + * DESCRIPTION: translate a pathname; return catalog information + */ +int v_resolve(hfsvol **vol, const char *path, + CatDataRec *data, long *parid, char *fname, node *np) +{ + unsigned long dirid; + char name[HFS_MAX_FLEN + 1], *nptr; + int found = 0; + + if (*path == 0) + ERROR(ENOENT, "empty path"); + + if (parid) + *parid = 0; + + nptr = strchr(path, ':'); + + if (*path == ':' || nptr == 0) + { + dirid = (*vol)->cwd; /* relative path */ + + if (*path == ':') + ++path; + + if (*path == 0) + { + found = v_getdthread(*vol, dirid, data, 0); + if (found == -1) + goto fail; + + if (found) + { + if (parid) + *parid = data->u.dthd.thdParID; + + found = v_catsearch(*vol, data->u.dthd.thdParID, + data->u.dthd.thdCName, data, fname, np); + if (found == -1) + goto fail; + } + + goto done; + } + } + else + { + hfsvol *check; + + dirid = HFS_CNID_ROOTPAR; /* absolute path */ + + if (nptr - path > HFS_MAX_VLEN) + ERROR(ENAMETOOLONG, 0); + + strncpy(name, path, nptr - path); + name[nptr - path] = 0; + + for (check = hfs_mounts; check; check = check->next) + { + if (d_relstring(check->mdb.drVN, name) == 0) + { + *vol = check; + break; + } + } + } + + while (1) + { + while (*path == ':') + { + ++path; + + found = v_getdthread(*vol, dirid, data, 0); + if (found == -1) + goto fail; + else if (! found) + goto done; + + dirid = data->u.dthd.thdParID; + } + + if (*path == 0) + { + found = v_getdthread(*vol, dirid, data, 0); + if (found == -1) + goto fail; + + if (found) + { + if (parid) + *parid = data->u.dthd.thdParID; + + found = v_catsearch(*vol, data->u.dthd.thdParID, + data->u.dthd.thdCName, data, fname, np); + if (found == -1) + goto fail; + } + + goto done; + } + + nptr = name; + while (nptr < name + sizeof(name) - 1 && *path && *path != ':') + *nptr++ = *path++; + + if (*path && *path != ':') + ERROR(ENAMETOOLONG, 0); + + *nptr = 0; + if (*path == ':') + ++path; + + if (parid) + *parid = dirid; + + found = v_catsearch(*vol, dirid, name, data, fname, np); + if (found == -1) + goto fail; + + if (! found) + { + if (*path && parid) + *parid = 0; + + if (*path == 0 && fname) + strcpy(fname, name); + + goto done; + } + + switch (data->cdrType) + { + case cdrDirRec: + if (*path == 0) + goto done; + + dirid = data->u.dir.dirDirID; + break; + + case cdrFilRec: + if (*path == 0) + goto done; + + ERROR(ENOTDIR, "invalid pathname"); + + default: + ERROR(EIO, "unexpected catalog record"); + } + } + +done: + return found; + +fail: + return -1; +} + +/* + * NAME: vol->adjvalence() + * DESCRIPTION: update a volume's valence counts + */ +int v_adjvalence(hfsvol *vol, unsigned long parid, int isdir, int adj) +{ + node n; + CatDataRec data; + int result = 0; + + if (isdir) + vol->mdb.drDirCnt += adj; + else + vol->mdb.drFilCnt += adj; + + vol->flags |= HFS_VOL_UPDATE_MDB; + + if (parid == HFS_CNID_ROOTDIR) + { + if (isdir) + vol->mdb.drNmRtDirs += adj; + else + vol->mdb.drNmFls += adj; + } + else if (parid == HFS_CNID_ROOTPAR) + goto done; + + if (v_getdthread(vol, parid, &data, 0) <= 0 || + v_catsearch(vol, data.u.dthd.thdParID, data.u.dthd.thdCName, + &data, 0, &n) <= 0 || + data.cdrType != cdrDirRec) + ERROR(EIO, "can't find parent directory"); + + data.u.dir.dirVal += adj; + data.u.dir.dirMdDat = d_mtime(time(0)); + + result = v_putcatrec(&data, &n); + +done: + return result; + +fail: + return -1; +} + +/* + * NAME: vol->mkdir() + * DESCRIPTION: create a new HFS directory + */ +int v_mkdir(hfsvol *vol, unsigned long parid, const char *name) +{ + CatKeyRec key; + CatDataRec data; + unsigned long id; + byte record[HFS_MAX_CATRECLEN]; + unsigned int reclen; + int i; + + if (bt_space(&vol->cat, 2) == -1) + goto fail; + + id = vol->mdb.drNxtCNID++; + vol->flags |= HFS_VOL_UPDATE_MDB; + + /* create directory record */ + + data.cdrType = cdrDirRec; + data.cdrResrv2 = 0; + + data.u.dir.dirFlags = 0; + data.u.dir.dirVal = 0; + data.u.dir.dirDirID = id; + data.u.dir.dirCrDat = d_mtime(time(0)); + data.u.dir.dirMdDat = data.u.dir.dirCrDat; + data.u.dir.dirBkDat = 0; + + memset(&data.u.dir.dirUsrInfo, 0, sizeof(data.u.dir.dirUsrInfo)); + memset(&data.u.dir.dirFndrInfo, 0, sizeof(data.u.dir.dirFndrInfo)); + for (i = 0; i < 4; ++i) + data.u.dir.dirResrv[i] = 0; + + r_makecatkey(&key, parid, name); + r_packcatrec(&key, &data, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1) + goto fail; + + /* create thread record */ + + data.cdrType = cdrThdRec; + data.cdrResrv2 = 0; + + data.u.dthd.thdResrv[0] = 0; + data.u.dthd.thdResrv[1] = 0; + data.u.dthd.thdParID = parid; + strcpy(data.u.dthd.thdCName, name); + + r_makecatkey(&key, id, ""); + r_packcatrec(&key, &data, record, &reclen); + + if (bt_insert(&vol->cat, record, reclen) == -1 || + v_adjvalence(vol, parid, 1, 1) == -1) + goto fail; + + return 0; + +fail: + return -1; +} + +/* + * NAME: markexts() + * DESCRIPTION: set bits from an extent record in the volume bitmap + */ +static +void markexts(block *vbm, const ExtDataRec *exts) +{ + int i; + unsigned int pt, len; + + for (i = 0; i < 3; ++i) + { + for ( pt = (*exts)[i].xdrStABN, + len = (*exts)[i].xdrNumABlks; len--; ++pt) + BMSET(vbm, pt); + } +} + +/* + * NAME: vol->scavenge() + * DESCRIPTION: safeguard blocks in the volume bitmap + */ +int v_scavenge(hfsvol *vol) +{ + block *vbm = vol->vbm; + node n; + unsigned int pt, blks; + unsigned long lastcnid = 15; + +# ifdef DEBUG + fprintf(stderr, "VOL: \"%s\" not cleanly unmounted\n", + vol->mdb.drVN); +# endif + + if (vol->flags & HFS_VOL_READONLY) + goto done; + +# ifdef DEBUG + fprintf(stderr, "VOL: scavenging...\n"); +# endif + + /* reset MDB by marking it dirty again */ + + vol->mdb.drAtrb |= HFS_ATRB_UMOUNTED; + if (v_dirty(vol) == -1) + goto fail; + + /* begin by marking extents in MDB */ + + markexts(vbm, &vol->mdb.drXTExtRec); + markexts(vbm, &vol->mdb.drCTExtRec); + + vol->flags |= HFS_VOL_UPDATE_VBM; + + /* scavenge the extents overflow file */ + + if (vol->ext.hdr.bthFNode > 0) + { + if (bt_getnode(&n, &vol->ext, vol->ext.hdr.bthFNode) == -1) + goto fail; + + n.rnum = 0; + + while (1) + { + ExtDataRec data; + const byte *ptr; + + while (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink > 0) + { + if (bt_getnode(&n, &vol->ext, n.nd.ndFLink) == -1) + goto fail; + + n.rnum = 0; + } + + if (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink == 0) + break; + + ptr = HFS_NODEREC(n, n.rnum); + r_unpackextdata(HFS_RECDATA(ptr), &data); + + markexts(vbm, &data); + + ++n.rnum; + } + } + + /* scavenge the catalog file */ + + if (vol->cat.hdr.bthFNode > 0) + { + if (bt_getnode(&n, &vol->cat, vol->cat.hdr.bthFNode) == -1) + goto fail; + + n.rnum = 0; + + while (1) + { + CatDataRec data; + const byte *ptr; + + while (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink > 0) + { + if (bt_getnode(&n, &vol->cat, n.nd.ndFLink) == -1) + goto fail; + + n.rnum = 0; + } + + if (n.rnum >= n.nd.ndNRecs && n.nd.ndFLink == 0) + break; + + ptr = HFS_NODEREC(n, n.rnum); + r_unpackcatdata(HFS_RECDATA(ptr), &data); + + switch (data.cdrType) + { + case cdrFilRec: + markexts(vbm, &data.u.fil.filExtRec); + markexts(vbm, &data.u.fil.filRExtRec); + + if (data.u.fil.filFlNum > lastcnid) + lastcnid = data.u.fil.filFlNum; + break; + + case cdrDirRec: + if (data.u.dir.dirDirID > lastcnid) + lastcnid = data.u.dir.dirDirID; + break; + } + + ++n.rnum; + } + } + + /* count free blocks */ + + for (blks = 0, pt = vol->mdb.drNmAlBlks; pt--; ) + { + if (! BMTST(vbm, pt)) + ++blks; + } + + if (vol->mdb.drFreeBks != blks) + { +# ifdef DEBUG + fprintf(stderr, "VOL: updating free blocks from %u to %u\n", + vol->mdb.drFreeBks, blks); +# endif + + vol->mdb.drFreeBks = blks; + vol->flags |= HFS_VOL_UPDATE_MDB; + } + + /* ensure next CNID is sane */ + + if ((unsigned long) vol->mdb.drNxtCNID <= lastcnid) + { +# ifdef DEBUG + fprintf(stderr, "VOL: updating next CNID from %lu to %lu\n", + vol->mdb.drNxtCNID, lastcnid + 1); +# endif + + vol->mdb.drNxtCNID = lastcnid + 1; + vol->flags |= HFS_VOL_UPDATE_MDB; + } + +# ifdef DEBUG + fprintf(stderr, "VOL: scavenging complete\n"); +# endif + +done: + return 0; + +fail: + return -1; +} diff --git a/libhfs/volume.h b/libhfs/volume.h new file mode 100644 index 0000000..ae8a093 --- /dev/null +++ b/libhfs/volume.h @@ -0,0 +1,62 @@ +/* + * libhfs - library for reading and writing Macintosh HFS volumes + * Copyright (C) 1996-1998 Robert Leslie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: volume.h,v 1.7 1998/11/02 22:09:12 rob Exp $ + */ + +void v_init(hfsvol *, int); + +int v_open(hfsvol *, const char *, int); +int v_flush(hfsvol *); +int v_close(hfsvol *); + +int v_same(hfsvol *, const char *); +int v_geometry(hfsvol *, int); + +int v_readmdb(hfsvol *); +int v_writemdb(hfsvol *); + +int v_readvbm(hfsvol *); +int v_writevbm(hfsvol *); + +int v_mount(hfsvol *); +int v_dirty(hfsvol *); + +int v_catsearch(hfsvol *, unsigned long, const char *, + CatDataRec *, char *, node *); +int v_extsearch(hfsfile *, unsigned int, ExtDataRec *, node *); + +int v_getthread(hfsvol *, unsigned long, CatDataRec *, node *, int); + +# define v_getdthread(vol, id, thread, np) \ + v_getthread(vol, id, thread, np, cdrThdRec) +# define v_getfthread(vol, id, thread, np) \ + v_getthread(vol, id, thread, np, cdrFThdRec) + +int v_putcatrec(const CatDataRec *, node *); +int v_putextrec(const ExtDataRec *, node *); + +int v_allocblocks(hfsvol *, ExtDescriptor *); +int v_freeblocks(hfsvol *, const ExtDescriptor *); + +int v_resolve(hfsvol **, const char *, CatDataRec *, long *, char *, node *); + +int v_adjvalence(hfsvol *, unsigned long, int, int); +int v_mkdir(hfsvol *, unsigned long, const char *); + +int v_scavenge(hfsvol *);