/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.fs.ext2;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.jnode.fs.FileSystemException;
import org.jnode.util.LittleEndian;
/**
* Ext2fs superblock
*
* @author Andras Nagy
*/
public class Superblock {
public static final int SUPERBLOCK_LENGTH = 1024;
// some constants for the fs creation
// one inode for 4KBs of data
private static final long BYTES_PER_INODE = 4096;
// 5% reserved for the superuser
private static final double RESERVED_BLOCKS_RATIO = 0.05;
// create the new fs with the sparse_super option
private static final boolean CREATE_WITH_SPARSE_SUPER = true;
// number of times to mount before check (check not yet implemented)
private static final int MAX_MOUNT_COUNT = 256;
// check every year (check not yet implemented)
private static final int CHECK_INTERVAL = 365 * 24 * 60 * 60;
// whatever
private static final long JNODE = 42;
private byte data[];
private boolean dirty;
private Ext2FileSystem fs;
private final Logger log = Logger.getLogger(getClass());
public Superblock() {
data = new byte[SUPERBLOCK_LENGTH];
}
public void read(byte src[], Ext2FileSystem fs) throws FileSystemException {
System.arraycopy(src, 0, data, 0, SUPERBLOCK_LENGTH);
this.fs = fs;
// check the magic :)
if (getMagic() != 0xEF53)
throw new FileSystemException("Not ext2 superblock (" + getMagic() + ": bad magic)");
setDirty(false);
}
public void create(BlockSize blockSize, Ext2FileSystem fs) throws IOException {
this.fs = fs;
setRevLevel(Ext2Constants.EXT2_DYNAMIC_REV);
setMinorRevLevel(0);
setMagic(0xEF53);
setCreatorOS(JNODE);
// the number of inodes has to be <= than the number of blocks
long bytesPerInode =
(BYTES_PER_INODE >= blockSize.getSize()) ? BYTES_PER_INODE : blockSize.getSize();
long size = fs.getApi().getLength();
long blocks = size / blockSize.getSize();
long inodes = size / bytesPerInode;
setINodesCount(inodes);
setBlocksCount(blocks);
setRBlocksCount((long) (RESERVED_BLOCKS_RATIO * blocks));
setDefResgid(0);
setDefResuid(0);
// actually sets the S_LOG_BLOCK_SIZE
setBlockSize(blockSize);
// set S_LOG_FRAG_SIZE
setFragSize(blockSize);
setFirstDataBlock(blockSize.getSize() == 1024 ? 1 : 0);
// a block bitmap is 1 block long, so blockSize*8 blocks can be indexed
// by a bitmap
// and thus be in a group
long blocksPerGroup = blockSize.getSize() << 3;
setBlocksPerGroup(blocksPerGroup);
setFragsPerGroup(blocksPerGroup);
long groupCount = Ext2Utils.ceilDiv(blocks, blocksPerGroup);
long inodesPerGroup = Ext2Utils.ceilDiv(inodes, groupCount);
setINodesPerGroup(inodesPerGroup);
// calculate the number of blocks reserved for metadata
// first, set the sparse_super option (it affects this value)
if (CREATE_WITH_SPARSE_SUPER)
setFeatureROCompat(getFeatureROCompat() |
Ext2Constants.EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER);
long sbSize = 1; // superblock is 1 block fixed
long gdtSize =
Ext2Utils.ceilDiv(groupCount * GroupDescriptor.GROUPDESCRIPTOR_LENGTH, blockSize
.getSize());
long bbSize = 1; // block bitmap is 1 block fixed
long ibSize = 1; // inode bitmap is 1 block fixed
long inodeTableSize =
Ext2Utils.ceilDiv(inodesPerGroup * INode.EXT2_GOOD_OLD_INODE_SIZE, blockSize.getSize());
int groupsWithMetadata = 0;
for (int i = 0; i < groupCount; i++)
if (fs.groupHasDescriptors(i))
groupsWithMetadata++;
long metadataSize =
(bbSize + ibSize + inodeTableSize) * groupCount + (sbSize + gdtSize) *
groupsWithMetadata;
setFreeBlocksCount(blocks - metadataSize);
setFirstInode(11);
setFreeInodesCount(inodes - getFirstInode() + 1);
setMTime(0);
setWTime(0);
setLastCheck(0);
setCheckInterval(CHECK_INTERVAL);
setMntCount(0);
setMaxMntCount(MAX_MOUNT_COUNT);
setState(Ext2Constants.EXT2_VALID_FS);
setErrors(Ext2Constants.EXT2_ERRORS_DEFAULT);
setINodeSize(INode.EXT2_GOOD_OLD_INODE_SIZE);
setBlockGroupNr(0);
// set the options SPARSE_SUPER and FILETYPE
setFeatureCompat(0);
setFeatureROCompat(Ext2Constants.EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER);
setFeatureIncompat(Ext2Constants.EXT2_FEATURE_INCOMPAT_FILETYPE);
byte[] uuid = new byte[16];
for (int i = 0; i < uuid.length; i++)
uuid[i] = (byte) (Math.random() * 255);
setUUID(uuid);
setPreallocBlocks(8);
setPreallocDirBlocks(0);
log.debug("SuperBlock.create(): getBlockSize(): " + getBlockSize());
}
/**
* Update the superblock copies on the disk
*/
public synchronized void update() throws IOException {
if (isDirty()) {
log.debug("Updating superblock copies");
byte[] oldData;
// update the main copy
if (getFirstDataBlock() == 0) {
oldData = fs.getBlock(0);
// the block size is an integer multiply of 1024, and if
// getFirstDataBlock==0, it's
// at least 2048 bytes
System.arraycopy(data, 0, oldData, 1024, SUPERBLOCK_LENGTH);
} else {
oldData = fs.getBlock(getFirstDataBlock());
System.arraycopy(data, 0, oldData, 0, SUPERBLOCK_LENGTH);
}
fs.writeBlock(getFirstDataBlock(), oldData, true);
// update the other copies
for (int i = 1; i < fs.getGroupCount(); i++) {
// check if there is a superblock copy in the block group
if (!fs.groupHasDescriptors(i))
continue;
long blockNr = getFirstDataBlock() + i * getBlocksPerGroup();
oldData = fs.getBlock(blockNr);
setBlockGroupNr(i);
// update the old contents with the new superblock
System.arraycopy(data, 0, oldData, 0, SUPERBLOCK_LENGTH);
fs.writeBlock(blockNr, oldData, true);
}
setBlockGroupNr(0);
setDirty(false);
}
}
// this field is only written during format (so no synchronization issues
// here)
public long getINodesCount() {
return LittleEndian.getUInt32(data, 0);
}
public void setINodesCount(long count) {
Ext2Utils.set32(data, 0, count);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getBlocksCount() {
return LittleEndian.getUInt32(data, 4);
}
public void setBlocksCount(long count) {
Ext2Utils.set32(data, 4, count);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getRBlocksCount() {
return LittleEndian.getUInt32(data, 8);
}
public void setRBlocksCount(long count) {
Ext2Utils.set32(data, 8, count);
setDirty(true);
}
public synchronized long getFreeBlocksCount() {
return LittleEndian.getUInt32(data, 12);
}
public synchronized void setFreeBlocksCount(long count) {
Ext2Utils.set32(data, 12, count);
setDirty(true);
}
public synchronized long getFreeInodesCount() {
return LittleEndian.getUInt32(data, 16);
}
public synchronized void setFreeInodesCount(long count) {
Ext2Utils.set32(data, 16, count);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getFirstDataBlock() {
return LittleEndian.getUInt32(data, 20);
}
public void setFirstDataBlock(long i) {
Ext2Utils.set32(data, 20, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
private long getLogBlockSize() {
return LittleEndian.getUInt32(data, 24);
}
private void setLogBlockSize(long i) {
Ext2Utils.set32(data, 24, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getBlockSize() {
return 1024 << getLogBlockSize();
}
public void setBlockSize(BlockSize size) {
// setLogBlockSize( (long)(Math.log(size)/Math.log(2) - 10) );
// Math.log() is buggy
// TODO should we handle all these values for size or not ? from mke2fs
// man page, it seems NO.
if (size.getSize() == 1024)
setLogBlockSize(0);
if (size.getSize() == 2048)
setLogBlockSize(1);
if (size.getSize() == 4096)
setLogFragSize(2);
if (size.getSize() == 8192)
setLogFragSize(3);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
private long getLogFragSize() {
return LittleEndian.getUInt32(data, 28);
}
private void setLogFragSize(long i) {
Ext2Utils.set32(data, 28, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getFragSize() {
if (getLogFragSize() > 0)
return 1024 << getLogFragSize();
else
return 1024 >> -getLogFragSize();
}
public void setFragSize(BlockSize size) {
// setLogFragSize( (long)(Math.log(size)/Math.log(2)) - 10 );
// Math.log() is buggy
// TODO should we handle all these values for size or not ? from mke2fs
// man page, it seems NO.
if (size.getSize() == 64)
setLogFragSize(-4);
if (size.getSize() == 128)
setLogFragSize(-3);
if (size.getSize() == 256)
setLogBlockSize(-2);
if (size.getSize() == 512)
setLogBlockSize(-1);
if (size.getSize() == 1024)
setLogFragSize(0);
if (size.getSize() == 2048)
setLogFragSize(1);
if (size.getSize() == 4096)
setLogBlockSize(2);
if (size.getSize() == 8192)
setLogBlockSize(3);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getBlocksPerGroup() {
return LittleEndian.getUInt32(data, 32);
}
public void setBlocksPerGroup(long i) {
Ext2Utils.set32(data, 32, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getFragsPerGroup() {
return LittleEndian.getUInt32(data, 36);
}
public void setFragsPerGroup(long i) {
Ext2Utils.set32(data, 36, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getINodesPerGroup() {
return LittleEndian.getUInt32(data, 40);
}
public void setINodesPerGroup(long i) {
Ext2Utils.set32(data, 40, i);
setDirty(true);
}
// this field is only written during mounting (so no synchronization issues
// here)
public long getMTime() {
return LittleEndian.getUInt32(data, 44);
}
public void setMTime(long time) {
Ext2Utils.set32(data, 44, time);
setDirty(true);
}
public synchronized long getWTime() {
return LittleEndian.getUInt32(data, 48);
}
public synchronized void setWTime(long time) {
Ext2Utils.set32(data, 48, time);
setDirty(true);
}
// this field is only written during mounting (so no synchronization issues
// here)
public int getMntCount() {
return LittleEndian.getUInt16(data, 52);
}
public void setMntCount(int i) {
LittleEndian.setInt16(data, 52, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getMaxMntCount() {
return LittleEndian.getUInt16(data, 54);
}
public void setMaxMntCount(int i) {
LittleEndian.setInt16(data, 54, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getMagic() {
return LittleEndian.getUInt16(data, 56);
}
public void setMagic(int i) {
LittleEndian.setInt16(data, 56, i);
setDirty(true);
}
public synchronized int getState() {
return LittleEndian.getUInt16(data, 58);
}
public synchronized void setState(int state) {
LittleEndian.setInt16(data, 58, state);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getErrors() {
return LittleEndian.getUInt16(data, 60);
}
public void setErrors(int i) {
LittleEndian.setInt16(data, 60, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getMinorRevLevel() {
return LittleEndian.getUInt16(data, 62);
}
public void setMinorRevLevel(int i) {
LittleEndian.setInt16(data, 62, i);
setDirty(true);
}
// this field is only written during filesystem check (so no synchronization
// issues here)
public long getLastCheck() {
return LittleEndian.getUInt32(data, 64);
}
public void setLastCheck(long i) {
Ext2Utils.set32(data, 64, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getCheckInterval() {
return LittleEndian.getUInt32(data, 68);
}
public void setCheckInterval(long i) {
Ext2Utils.set32(data, 68, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getCreatorOS() {
return LittleEndian.getUInt32(data, 72);
}
public void setCreatorOS(long i) {
Ext2Utils.set32(data, 72, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getRevLevel() {
return LittleEndian.getUInt32(data, 76);
}
public void setRevLevel(long i) {
Ext2Utils.set32(data, 76, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getDefResuid() {
return LittleEndian.getUInt16(data, 80);
}
public void setDefResuid(int i) {
LittleEndian.setInt16(data, 80, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getDefResgid() {
return LittleEndian.getUInt16(data, 82);
}
public void setDefResgid(int i) {
LittleEndian.setInt16(data, 82, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getFirstInode() {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
return LittleEndian.getUInt32(data, 84);
else
return 11;
}
public void setFirstInode(long i) {
Ext2Utils.set32(data, 84, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public int getINodeSize() {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
return LittleEndian.getUInt16(data, 88);
else
return INode.EXT2_GOOD_OLD_INODE_SIZE;
}
public void setINodeSize(int i) {
LittleEndian.setInt16(data, 88, i);
setDirty(true);
}
// XXX what to return for old versions?
public synchronized long getBlockGroupNr() {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
return LittleEndian.getUInt16(data, 90);
else
return 0;
}
public synchronized void setBlockGroupNr(int i) {
LittleEndian.setInt16(data, 90, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getFeatureCompat() {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
return LittleEndian.getUInt32(data, 92);
else
return 0;
}
public void setFeatureCompat(long i) {
Ext2Utils.set32(data, 92, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getFeatureIncompat() {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
return LittleEndian.getUInt32(data, 96);
else
return 0;
}
public void setFeatureIncompat(long i) {
Ext2Utils.set32(data, 96, i);
setDirty(true);
}
// this field is only written during format (so no synchronization issues
// here)
public long getFeatureROCompat() {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
return LittleEndian.getUInt32(data, 100);
else
return 0;
}
public void setFeatureROCompat(long i) {
Ext2Utils.set32(data, 100, i);
setDirty(true);
}
//this field is only written during format (so no synchronization issues here)
public byte[] getUUID() {
byte[] result = new byte[16];
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
System.arraycopy(data, 104, result, 0, 16);
return result;
}
public void setUUID(byte[] uuid) {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
System.arraycopy(uuid, 0, data, 104, 16);
setDirty(true);
}
public String getVolumeName() {
StringBuffer result = new StringBuffer();
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
for (int i = 0; i < 16; i++) {
char c = (char) data[120 + i];
if (c != 0)
result.append(c);
else
break;
}
return result.toString();
}
public String getLastMounted() {
StringBuffer result = new StringBuffer();
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
for (int i = 0; i < 64; i++) {
char c = (char) data[136 + i];
if (c != 0)
result.append(c);
else
break;
}
return result.toString();
}
//not sure this is the correct byte-order for this field
public long getAlgoBitmap() {
if (getRevLevel() == Ext2Constants.EXT2_DYNAMIC_REV)
return LittleEndian.getUInt32(data, 200);
else
return 11;
}
//this field is only written during format (so no synchronization issues here)
public int getPreallocBlocks() {
return LittleEndian.getUInt8(data, 204);
}
public void setPreallocBlocks(int i) {
Ext2Utils.set8(data, 204, i);
setDirty(true);
}
//this field is only written during format (so no synchronization issues here)
public int getPreallocDirBlocks() {
return LittleEndian.getUInt8(data, 205);
}
public void setPreallocDirBlocks(int i) {
Ext2Utils.set8(data, 205, i);
setDirty(true);
}
public byte[] getJournalUUID() {
byte[] result = new byte[16];
System.arraycopy(data, 208, result, 0, 16);
return result;
}
public long getJournalINum() {
return LittleEndian.getUInt32(data, 224);
}
public long getJournalDev() {
return LittleEndian.getUInt32(data, 228);
}
public long getLastOrphan() {
return LittleEndian.getUInt8(data, 232);
}
/**
* Gets the block number that contains the multi-mount protection (MMP) data.
*
* @return the block number.
*/
public long getMultiMountProtectionBlock() {
return LittleEndian.getInt64(data, 360);
}
public long getBlocksPerFlex() {
int logBlocksPerFlex = LittleEndian.getUInt8(data, 372);
return 1L << logBlocksPerFlex;
}
/**
* Checks whether the file system is using flexible block groups.
*
* @return {@code true} if using flexible block groups.
*/
public boolean isUsingFlexibleBlockGroups() {
return (getFeatureIncompat() & Ext2Constants.EXT4_FEATURE_INCOMPAT_FLEX_BG) ==
Ext2Constants.EXT4_FEATURE_INCOMPAT_FLEX_BG;
}
/**
* @return the SuperBlock's dirty flag
*/
public boolean isDirty() {
return dirty;
}
/**
* @param b
*/
public void setDirty(boolean b) {
dirty = b;
}
}