package org.opendedup.sdfs.io; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import org.opendedup.sdfs.Main; import org.opendedup.sdfs.filestore.DedupFileStore; import org.opendedup.sdfs.filestore.MetaFileStore; import org.opendedup.sdfs.monitor.IOMonitor; import org.opendedup.util.ByteUtils; import com.eaio.uuid.UUID; /** * * @author annesam Stores Meta-Data about a dedupFile. This class is modeled * from the java.io.File class. Meta-Data files are stored within the * MetaDataFileStore @see com.annesam.filestore.MetaDataFileStore */ public class MetaDataDedupFile implements java.io.Externalizable { private static final long serialVersionUID = -4598940197202968523L; transient public static final String pathSeparator = File.pathSeparator; transient public static final String separator = File.separator; transient public static final char pathSeparatorChar = File.pathSeparatorChar; transient public static final char separatorChar = File.separatorChar; transient private static Logger log = Logger.getLogger("sdfs"); protected long timeStamp = 0; private long length = 0; private String path = ""; private long lastModified = 0; private long lastAccessed = 0; private boolean execute = true; private boolean read = true; private boolean write = true; private boolean directory = false; private boolean hidden = false; private boolean ownerWriteOnly = false; private boolean ownerExecOnly = false; private boolean ownerReadOnly = false; private String dfGuid = null; private String guid = ""; private IOMonitor monitor; private boolean vmdk; private int permissions; private int owner_id; private int group_id; private HashMap<String, String> extendedAttrs = new HashMap<String, String>(); private boolean dedup = Main.dedupFiles; /** * * @return true if all chunks within the file will be deduped. */ public boolean isDedup() { return dedup; } /** * * @param dedup * if true all chunks will be deduped, Otherwise chunks will be * deduped opportunistically. */ public void setDedup(boolean dedupNow) { if (!this.dedup && dedupNow) { try { this.dedup = dedupNow; this.getDedupFile().optimize(this.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.dedup = dedupNow; } /** * adds a posix extended attribute * * @param name * the name of the attribute * @param value * the value of the attribute */ public void addXAttribute(String name, String value) { extendedAttrs.put(name, value); } /** * returns an extended attribute for a give name * * @param name * @return the extended attribute */ public String getXAttribute(String name) { if (this.extendedAttrs.containsKey(name)) return extendedAttrs.get(name); else return "-1"; } /** * * @return list of all extended attribute names */ public String[] getXAttersNames() { String[] keys = new String[this.extendedAttrs.size()]; Iterator<String> iter = this.extendedAttrs.keySet().iterator(); int i = 0; while (iter.hasNext()) { keys[i] = iter.next(); i++; } return keys; } /** * * @return posix permissions e.g. 0777 */ public int getPermissions() { return permissions; } /** * * @param permissions * sets permissions */ public void setPermissions(int permissions) { this.permissions = permissions; } /** * * @return the file owner id */ public int getOwner_id() { return owner_id; } /** * * @param owner_id * sets the file owner id */ public void setOwner_id(int owner_id) { this.owner_id = owner_id; } /** * * @return returns the group owner id */ public int getGroup_id() { return group_id; } /** * * @param group_id * sets the group owner id */ public void setGroup_id(int group_id) { this.group_id = group_id; } /** * * @return true if this file is a vmdk */ public boolean isVmdk() { return vmdk; } /** * * @param vmdk * flags this file as a vmdk if true */ public void setVmdk(boolean vmdk) { this.vmdk = vmdk; } public MetaDataDedupFile() { } public static MetaDataDedupFile getFile(String path) { File f = new File(path); MetaDataDedupFile mf = null; if(!f.exists() || f.isDirectory()) { mf = new MetaDataDedupFile(path); } else { try { ObjectInputStream in = new ObjectInputStream( new FileInputStream(path)); mf = (MetaDataDedupFile)in.readObject(); mf.path = path; } catch (Exception e) { log.log(Level.SEVERE,"unable to de-serialize " +path,e); } } return mf; } /** * * @return returns the IOMonitor for this file. IOMonitors monitor * reads,writes, and dedup rate. */ public IOMonitor getIOMonitor() { if (monitor == null) monitor = new IOMonitor(); return monitor; } /** * * @param path * the path to the dedup file. */ private MetaDataDedupFile(String path) { init(path); } /** * * @param parent * the parent folder * @param child * the file name */ public MetaDataDedupFile(File parent, String child) { String pth = parent.getAbsolutePath() + File.separator + child; init(pth); } /** * * @return the DedupFile associated with this file. It will create one if it * does not already exist. * @throws IOException */ public synchronized DedupFile getDedupFile() throws IOException { if (this.dfGuid == null) { DedupFile df = DedupFileStore.getDedupFile(this); this.dfGuid = df.getGUID(); log.finer("No DF EXISTS .... Set dedup file for " + this.getPath() + " to " + this.dfGuid); this.sync(); return df; } else { return DedupFileStore.getDedupFile(this); } } /** * * @return the guid associated with this file */ public String getGUID() { return guid; } /** * Clones a file and the underlying DedupFile * * @param snaptoPath * the path to clone to * @param overwrite * if true, it will overwrite the destination file if it alreay * exists * @return the new clone * @throws IOException */ public MetaDataDedupFile snapshot(String snaptoPath, boolean overwrite) throws IOException { if (!this.isDirectory()) { File f = new File(snaptoPath); if (f.exists() && !overwrite) throw new IOException("path exists [" + snaptoPath + "]Cannot overwrite existing data "); if (!f.getParentFile().exists()) f.getParentFile().mkdirs(); MetaDataDedupFile _mf = new MetaDataDedupFile(snaptoPath); _mf.directory = this.directory; _mf.execute = this.execute; _mf.hidden = this.hidden; _mf.lastModified = this.lastModified; _mf.setLength(this.length, false); _mf.ownerExecOnly = this.ownerExecOnly; _mf.ownerReadOnly = this.ownerReadOnly; _mf.ownerWriteOnly = this.ownerWriteOnly; _mf.timeStamp = this.timeStamp; _mf.read = this.read; _mf.write = this.write; _mf.owner_id = this.owner_id; _mf.group_id = this.group_id; _mf.permissions = this.permissions; _mf.dedup = this.dedup; _mf.dfGuid = DedupFileStore.cloneDedupFile(this, _mf).getGUID(); _mf.getIOMonitor().setVirtualBytesWritten(this.length()); _mf.getIOMonitor().setDuplicateBlocks( this.getIOMonitor().getDuplicateBlocks()); _mf.setVmdk(this.isVmdk()); _mf.unmarshal(); return _mf; } else { File f = new File(snaptoPath); f.mkdirs(); int trimlen = this.getPath().length(); MetaDataDedupFile[] files = this.listFiles(); for (int i = 0; i < files.length; i++) { MetaDataDedupFile file = files[i]; String newPath = snaptoPath + File.separator + file.getPath().substring(trimlen); file.snapshot(newPath, overwrite); } return MetaFileStore.getMF(snaptoPath); } } /** * initiates the MetaDataDedupFile * * @param path * the path to the file */ private void init(String path) { this.lastAccessed = System.currentTimeMillis(); this.path = path; File f = new File(path); if (!f.exists()) { log.finer("Creating new MetaFile for " + this.path); this.guid = new UUID().toString(); monitor = new IOMonitor(); this.owner_id = Main.defaultOwner; this.group_id = Main.defaultGroup; this.permissions = Main.defaultFilePermissions; this.lastModified = System.currentTimeMillis(); this.dedup = Main.dedupFiles; this.timeStamp = System.currentTimeMillis(); this.setLength(0, false); } else if (f.isDirectory()) { this.permissions = Main.defaultDirPermissions; this.owner_id = Main.defaultOwner; this.group_id = Main.defaultGroup; this.directory = true; this.length = 4096; } } /** * Writes the stub for this file to disk. Stubs are pointers written to a * file system that map to virtual filesystem directory and file structure. * The stub only contains the guid associated with the file in question. * * @return true if written */ private synchronized boolean writeFile() { File f = new File(this.path); if (!f.isDirectory()) { if (!f.getParentFile().exists()) f.getParentFile().mkdirs(); try { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(this.path)); out.writeObject(this); out.close(); } catch (Exception e) { log.log(Level.WARNING, "unable to write file metadata for [" + this.path + "]", e); return false; } return true; } return true; } /** * Serializes the file to the MetaFileStore * * @return true if serialized */ public boolean unmarshal() { return this.writeFile(); } /** * * @return returns the GUID for the underlying DedupFile */ public String getDfGuid() { return dfGuid; } /** * * @return time when file was last modified */ public long lastModified() { if (this.isDirectory()) return new File(this.path).lastModified(); return lastModified; } /** * * @return creates a blank new file */ public boolean createNewFile() { return this.unmarshal(); } /** * * @return true if hidden */ public boolean isHidden() { return hidden; } /** * * @param hidden * true if hidden */ public void setHidden(boolean hidden) { this.hidden = hidden; this.unmarshal(); } /** * * @return true if deleted */ public boolean deleteStub() { File f = new File(this.path); return f.delete(); } /** * * @param df * the DedupFile that will be referenced within this file */ protected void setDedupFile(DedupFile df) { this.dfGuid = df.getGUID(); } /** * Delete this file only. This is used to delete the file in question but * not the DedupFile Reference * */ protected synchronized boolean deleteSelf() { File f = new File(this.path); MetaFileStore.removedCachedMF(this.path); return f.delete(); } /** * * @return the children of this directory and null if it is not a directory. */ public String[] list() { File f = new File(this.path); if (f.isDirectory()) { return f.list(); } else { return null; } } /** * * @return the children as MetaDataDedupFiles or null if it is not a * directory */ public MetaDataDedupFile[] listFiles() { File f = new File(this.path); if (f.isDirectory()) { String[] files = f.list(); MetaDataDedupFile[] df = new MetaDataDedupFile[files.length]; for (int i = 0; i < df.length; i++) { df[i] = MetaFileStore.getMF(this.getPath() + File.separator + files[i]); } return df; } else { return null; } } public boolean mkdir() { this.directory = true; File f = new File(this.path); return f.mkdir(); } public boolean mkdirs() { File f = new File(this.path); return f.mkdirs(); } public boolean renameTo(String dest) { File f = new File(this.path); if (f.isDirectory()) { return f.renameTo(new File(dest)); } else { boolean rename = f.renameTo(new File(dest)); if (rename) { MetaFileStore.rename(this.path, dest, this); this.path = dest; this.unmarshal(); } else { log.info("unable to move file"); } return rename; } } public boolean exists() { return new File(this.path).exists(); } public String getAbsolutePath() { return this.path; } public String getCanonicalPath() { return this.path; } public String getParent() { return new File(this.path).getParent(); } public boolean canExecute() { return execute; } public boolean canRead() { return this.read; } public boolean canWrite() { return this.write; } public boolean setExecutable(boolean executable, boolean ownerOnly) { this.execute = executable; this.ownerExecOnly = ownerOnly; this.unmarshal(); return true; } public boolean setExecutable(boolean executable) { this.execute = executable; this.unmarshal(); return true; } public boolean setWritable(boolean writable, boolean ownerOnly) { this.write = writable; this.ownerWriteOnly = ownerOnly; this.unmarshal(); return true; } public boolean setWritable(boolean writable) { this.write = writable; this.unmarshal(); return true; } public boolean setReadable(boolean readable, boolean ownerOnly) { this.read = readable; this.ownerReadOnly = ownerOnly; this.unmarshal(); return true; } public boolean setReadable(boolean readable) { this.read = readable; this.unmarshal(); return true; } public void setReadOnly() { this.read = true; this.unmarshal(); } public boolean isFile() { return new File(this.path).isFile(); } public boolean isDirectory() { return new File(this.path).isDirectory(); } public String getName() { return new File(this.path).getName(); } /** * @param lastModified * the lastModified to set */ public boolean setLastModified(long lastModified) { this.lastModified = lastModified; this.lastAccessed = lastModified; return true; } /** * @return the timeStamp */ public long getTimeStamp() { return timeStamp; } /** * @param timeStamp * the timeStamp to set */ public void setTimeStamp(long timeStamp, boolean serialize) { this.timeStamp = timeStamp; if (serialize) this.unmarshal(); } /** * @return the length */ public long length() { return length; } /** * @param length * the length to set */ public void setLength(long l, boolean serialize) { long len = l - this.length; Main.volume.updateCurrentSize(len); this.length = l; if (serialize) this.unmarshal(); } /** * @return the path to the file stub on disk */ public String getPath() { return path; } /** * * @param filePath * the path to the file * @return true if the file exists */ public static boolean exists(String filePath) { File f = new File(filePath); return f.exists(); } public boolean isAbsolute() { return true; } public int hashCode() { return new File(this.path).hashCode(); } /** * writes all the metadata and the Dedup blocks to the dedup chunk service */ public void sync() { this.unmarshal(); } /** * * @param lastAccessed */ public void setLastAccessed(long lastAccessed) { this.lastAccessed = lastAccessed; } public long getLastAccessed() { return lastAccessed; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.timeStamp = in.readLong(); this.length = in.readLong(); this.lastModified = in.readLong(); this.lastAccessed = in.readLong(); this.execute = in.readBoolean(); this.read = in.readBoolean(); this.write = in.readBoolean(); this.directory = in.readBoolean(); this.hidden = in.readBoolean(); this.ownerWriteOnly = in.readBoolean(); this.ownerExecOnly = in.readBoolean(); this.ownerReadOnly = in.readBoolean(); int dfgl = in.readInt(); if (dfgl == -1) { this.dfGuid = null; } else { byte[] dfb = new byte[dfgl]; in.read(dfb); this.dfGuid = new String(dfb); } int gl = in.readInt(); byte[] gfb = new byte[gl]; in.read(gfb); this.guid = new String(gfb); int ml = in.readInt(); if (ml == -1) { this.monitor = null; } else { byte[] mlb = new byte[ml]; in.read(mlb); this.guid = new String(mlb); } this.vmdk = in.readBoolean(); this.owner_id = in.readInt(); this.group_id = in.readInt(); byte[] hmb = new byte[in.readInt()]; this.extendedAttrs = ByteUtils.deSerializeHashMap(hmb); this.dedup = in.readBoolean(); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeLong(timeStamp); out.writeLong(length); out.writeLong(lastModified); out.writeLong(lastAccessed); out.writeBoolean(execute); out.writeBoolean(read); out.writeBoolean(write); out.writeBoolean(directory); out.writeBoolean(hidden); out.writeBoolean(ownerWriteOnly); out.writeBoolean(ownerExecOnly); out.writeBoolean(ownerReadOnly); if (this.dfGuid != null) { byte[] dfb = this.dfGuid.getBytes(); out.writeInt(dfb.length); out.write(dfb); } else { out.writeInt(-1); } byte[] dfb = this.guid.getBytes(); out.writeInt(dfb.length); out.write(dfb); if (this.monitor != null) { byte[] mfb = this.monitor.toByteArray(); out.writeInt(mfb.length); out.write(mfb); } else { out.writeInt(-1); } out.writeBoolean(vmdk); out.writeInt(owner_id); out.writeInt(group_id); byte[] hmb = ByteUtils.serializeHashMap(extendedAttrs); out.writeInt(hmb.length); out.write(hmb); out.writeBoolean(dedup); } }