/*- * Copyright (C) 2008-2014 Erik Larsson * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package org.catacombae.storage.fs.hfsplus; import org.catacombae.hfs.HFSVolume; import org.catacombae.storage.fs.hfscommon.HFSCommonFileSystemHandler; import org.catacombae.storage.io.DataLocator; import org.catacombae.hfs.plus.HFSPlusVolume; import org.catacombae.hfs.types.hfscommon.CommonHFSCatalogFileRecord; import org.catacombae.hfs.types.hfscommon.CommonHFSCatalogFolderRecord; import org.catacombae.hfs.types.hfscommon.CommonHFSCatalogLeafRecord; import org.catacombae.hfs.types.hfscommon.CommonHFSCatalogNodeID; import org.catacombae.storage.fs.FSEntry; import org.catacombae.storage.fs.FSFile; import org.catacombae.storage.fs.hfscommon.HFSCommonFSLink; import org.catacombae.util.IOUtil; import org.catacombae.util.Util; /** * @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a> */ public class HFSPlusFileSystemHandler extends HFSCommonFileSystemHandler { private static final String FILE_HARD_LINK_DIR = "\u0000\u0000\u0000\u0000HFS+ Private Data"; private static final String FILE_HARD_LINK_PREFIX = "iNode"; private static final String DIRECTORY_HARD_LINK_DIR = ".HFS+ Private Directory Data" + (char)0x0d; private static final String DIRECTORY_HARD_LINK_PREFIX = "dir_"; private static final String JOURNAL_INFO_BLOCK_FILE = ".journal_info_block"; private static final String JOURNAL_FILE = ".journal"; public HFSPlusFileSystemHandler(DataLocator fsLocator, boolean useCaching, boolean posixNames, boolean doUnicodeFileNameComposition, boolean hideProtected) { super(new HFSPlusVolume(fsLocator.createReadOnlyFile(), useCaching), posixNames, doUnicodeFileNameComposition, hideProtected); } protected HFSPlusFileSystemHandler(HFSVolume vol, boolean posixNames, boolean doUnicodeFileNameComposition, boolean hideProtected) { super(vol, posixNames, doUnicodeFileNameComposition, hideProtected); } protected boolean shouldHide(CommonHFSCatalogLeafRecord rec) { // The only folder that contains hidden files is the root folder. CommonHFSCatalogNodeID parentID = rec.getKey().getParentID(); if(!parentID.equals(parentID.getReservedID(CommonHFSCatalogNodeID. ReservedID.ROOT_FOLDER))) { return false; } String name = view.decodeString(rec.getKey().getNodeName()); if(rec instanceof CommonHFSCatalogFileRecord) { if(name.equals(JOURNAL_INFO_BLOCK_FILE)) return hideProtected; if(name.equals(JOURNAL_FILE)) return hideProtected; } else if(rec instanceof CommonHFSCatalogFolderRecord) { if(name.equals(FILE_HARD_LINK_DIR)) return hideProtected; if(name.equals(DIRECTORY_HARD_LINK_DIR)) return hideProtected; } return false; } private String[] getHardFileLinkPath(int inodeNumber) { return new String[] { FILE_HARD_LINK_DIR, FILE_HARD_LINK_PREFIX + Util.unsign(inodeNumber) }; } private String[] getHardDirectoryLinkPath(int inodeNumber) { return new String[] { DIRECTORY_HARD_LINK_DIR, DIRECTORY_HARD_LINK_PREFIX + Util.unsign(inodeNumber) }; } @Override protected FSEntry entryFromRecord(CommonHFSCatalogFileRecord fileRecord) { if(fileRecord.getData().isSymbolicLink()) return new HFSCommonFSLink(this, fileRecord); else if(fileRecord.getData().isHardFileLink()) { CommonHFSCatalogFileRecord iNode = lookupFileInode(fileRecord.getData().getHardLinkInode()); if(iNode != null) { return createFSFile(fileRecord, iNode); } else { System.err.println("Looking up file iNode " + fileRecord.getData().getHardLinkInode() + " (" + fileRecord.getKey().getParentID().toLong() + ":\"" + getProperNodeName(fileRecord) + "\") FAILED!"); return createFSFile(fileRecord); } } else if(fileRecord.getData().isHardDirectoryLink()) { CommonHFSCatalogFolderRecord iNode = lookupDirectoryInode(fileRecord.getData(). getHardLinkInode()); if(iNode != null) { return createFSFolder(fileRecord, iNode); } else { System.err.println("Looking up directory iNode " + fileRecord.getData().getHardLinkInode() + " (" + fileRecord.getKey().getParentID().toLong() + ":\"" + getProperNodeName(fileRecord) + "\") FAILED!"); return createFSFile(fileRecord); } } else { return super.entryFromRecord(fileRecord); } } private CommonHFSCatalogFileRecord lookupFileInode(int inodeNumber) { long trueInodeNumber = Util.unsign(inodeNumber); CommonHFSCatalogLeafRecord res = getRecord(view.getCatalogFile().getRootFolder(), FILE_HARD_LINK_DIR, FILE_HARD_LINK_PREFIX + trueInodeNumber); if(res == null) { // Could not find any inode return null; } else if(res instanceof CommonHFSCatalogFileRecord) { return (CommonHFSCatalogFileRecord) res; } else { throw new RuntimeException("Error in HFS+ file system structure: " + "Found a " + res.getClass() + " in file hard link dir " + "for iNode" + trueInodeNumber); } } private CommonHFSCatalogFolderRecord lookupDirectoryInode(int inodeNumber) { long trueInodeNumber = Util.unsign(inodeNumber); CommonHFSCatalogLeafRecord res = getRecord(view.getCatalogFile().getRootFolder(), DIRECTORY_HARD_LINK_DIR, DIRECTORY_HARD_LINK_PREFIX + trueInodeNumber); if(res == null) { // Could not find any inode return null; } else if(res instanceof CommonHFSCatalogFolderRecord) { return (CommonHFSCatalogFolderRecord) res; } else { throw new RuntimeException("Error in HFS+ file system structure: " + "Found a " + res.getClass() + " in directory hard link " + "dir for dir_" + trueInodeNumber); } } protected Long getLinkCount(CommonHFSCatalogFileRecord fr) { if(fr.getData().isHardFileLink()) { int inodeNumber = fr.getData().getHardLinkInode(); CommonHFSCatalogFileRecord rec = lookupFileInode(inodeNumber); return Util.unsign(rec.getData().getPermissions().getSpecial()); } else if(fr.getData().isHardDirectoryLink()) { int inodeNumber = fr.getData().getHardLinkInode(); CommonHFSCatalogFolderRecord rec = lookupDirectoryInode(inodeNumber); return Util.unsign(rec.getData().getPermissions().getSpecial()); } else { return null; } } protected String[] getAbsoluteLinkPath(String[] path, int pathLength, CommonHFSCatalogFileRecord rec) { String[] absPath; if(rec.getData().isSymbolicLink()) { byte[] data = IOUtil.readFully(getReadableDataForkStream(rec)); String posixPath = Util.readString(data, "UTF-8"); String[] basePath = Util.arrayCopy(path, 0, new String[pathLength - 1], 0, pathLength - 1); absPath = getTruePathFromPosixPath(posixPath, basePath); if(absPath == null) { // Sorry pal, no luck in finding your link target // log(prefix + " getRecord: no link target found for posix " + // "path \"" + posixPath + "\" with base path \"" + // Util.concatenateStrings(basePath, "/") + "\""); return null; } else { // log(prefix + " getRecord: absPath=" + // Util.concatenateStrings(absPath, "/")); } } else if(rec.getData().isHardFileLink()) { absPath = getHardFileLinkPath(rec.getData().getHardLinkInode()); } else if(rec.getData().isHardDirectoryLink()) { absPath = getHardDirectoryLinkPath(rec.getData(). getHardLinkInode()); } else { absPath = null; } return absPath; } @Override protected FSFile newFSFile(CommonHFSCatalogFileRecord fileRecord) { return new HFSPlusFSFile(this, fileRecord); } @Override protected FSFile newFSFile(CommonHFSCatalogFileRecord hardLinkRecord, CommonHFSCatalogFileRecord fileRecord) { return new HFSPlusFSFile(this, hardLinkRecord, fileRecord); } }