/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
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 3 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, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.io.fat;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import xxl.core.io.fat.errors.InitializationException;
import xxl.core.io.fat.errors.WrongFATType;
import xxl.core.io.fat.errors.WrongLength;
import xxl.core.io.fat.util.MyMath;
import xxl.core.io.raw.NativeRawAccess;
import xxl.core.io.raw.RAFRawAccess;
import xxl.core.io.raw.RAMRawAccess;
import xxl.core.io.raw.RawAccess;
import xxl.core.util.WrappingRuntimeException;
/**
* This class represents a file system. The file system manages devices. Each device is a
* raw partition, RandomAccessFile, or a RAM area. For each device there is one RawAccess
* object which supports the low level I/O-Operations on it.
*/
public class FileSystem
{
/**
* Iff the debug flag is set you get some debug messages like bpb, fat,
* fsi, and directory structure.
*/
public static boolean debug = false;
/**
* Indicates the type of action to do. Boot the device of the file system.
*/
public static final byte BOOT = 0;
/**
* Indicates the type of action to do. Format the device of the file system.
*/
public static final byte FORMAT = 1;
/**
* This file represents a master-boot-record. In that file
* all bootable devices and information about them are listed.
* Each time a new device is created, all necessary information
* about it will be saved at this file. The next time the file
* system will start, all devices stored at the bootFile
* will booted automatically.
*/
private String bootFileName;
/**
* List of all active devices like floppys, partitions etc.
*/
private List devices = new LinkedList();
/**
* The extension of a RandomAccessFile needs a file
* that exists inside the OS-file system. The file is never used,
* just opened read only and immediately closed again. This field
* only exists because of the inflexible implementation of RandomAccessFile.
*/
private File dummyFile;
/**
* Output stream for messages.
*/
private PrintStream out;
/**
* This class contains information about the device objects.
*/
public class DeviceInformation
{
/**
* FATDevice object.
*/
protected FATDevice device;
/**
* Number of users of the device stored at this class.
*/
protected int numberOfUsers;
/**
* Create an instance of this object with the given device.
* @param device the device object to store.
*/
public DeviceInformation(FATDevice device)
{
this.device = device;
numberOfUsers = 1;
} //end constructor
/**
* Add one user to the number of users that use this device.
*/
public void addUser()
{
numberOfUsers++;
} //end addUser()
/**
* Remove one user and return number of active user.
* @return the number of remaining users.
*/
public int removeUser()
{
numberOfUsers--;
return numberOfUsers;
} //end removeUser()
/**
* Return the number of users of the device object.
* @return the number of users.
*/
public int getNumOfUsers()
{
return numberOfUsers;
} //end getNumberOfUsers()
/**
* Return the stored device object.
* @return the stored device object.
*/
public FATDevice getDevice()
{
return device;
} //end getDevice()
} //end inner class DeviceInformation
/**
* Remove the whole line where the device with deviceName is stored
* from the file where all bootable devices are listed.
* @param deviceName the name of the device.
* @param bootFileName Name of the file where the information of the devices is stored.
* @return true in case the operation was succesfull; false otherwise.
*/
public static boolean removeLine(String deviceName, String bootFileName)
{
try
{
RandomAccessFile raf;
raf = new RandomAccessFile(bootFileName, "rw");
raf.seek(0);
long fileLength = raf.length();
long tempLength = 0;
boolean found = false;
//search the line where deviceName is stored
while(raf.getFilePointer() < fileLength)
{
tempLength = raf.getFilePointer();
String line = raf.readLine();
StringTokenizer st = new StringTokenizer(line, "\t");
String name = "";
if (st.hasMoreTokens())
name = st.nextToken();
else
continue;
if (name.equals(deviceName))
{
found = true;
break;
}
}
if (!found)
return false;
//skip the line that should be deleted
if (raf.getFilePointer() < fileLength)
raf.readLine();
Vector lines = new Vector();
while(raf.getFilePointer() < fileLength)
{
String line = raf.readLine();
lines.add(line);
}
//set file size to tempLength
raf.setLength(tempLength);
if (raf.length() != 0)
raf.write(0x0A); //lf
//write the saved lines back to the file
for (int i=0; i < lines.size(); i++)
{
raf.writeBytes((String)lines.get(i));
raf.write(0x0D); //cr
raf.write(0x0A); //lf
}
raf.close();
}
catch (IOException e)
{
throw new WrappingRuntimeException(e);
}
return true;
} //end removeLine(String deviceName)
/**
* Return the lines of the bootFile as a string array.
* If the file is empty the returned string array will
* be empty, too.
*
* @param bootFileName Name of the file where the information of the devices is stored.
* @return the lines of the bootFile or an empty string
* array if there is no content.
*/
public static String[] getBootFileContent(String bootFileName)
{
Vector lines = new Vector();
try
{
RandomAccessFile raf;
raf = new RandomAccessFile(bootFileName, "rw");
raf.seek(0);
long fileLength = raf.length();
while(raf.getFilePointer() < fileLength)
{
String line = raf.readLine();
lines.add(line);
}
raf.close();
}
catch(IOException e)
{
throw new WrappingRuntimeException(e);
}
return (String[])(lines.toArray(new String[0]));
} //end getBootFileContent()
/**
* Check if the given device name eqauls a unix device name. That is
* the name has as prefix "/dev/X", where X is the name of the device.
* @param name the name of the device.
* @return true in case the given name eqauls a unic device name, otherwise
* false is returned.
*/
public static boolean isUnixDeviceName(String name)
{
if (name.startsWith("/dev/") && name.length() > "/dev/".length())
return true;
return false;
}
/**
* Return a file system object.
* @param bootFileName Name of the file where the information of the devices is stored.
* @param out Output stream for some messages of the FATDevice. You can use System.out for
* example.
* @param dummyFile The extension of a RandomAccessFile needs a file
* that exists inside the OS-file system. The file is never used,
* just opened read only and immediately closed again. This parameter
* only exists because of the inflexible implementation of RandomAccessFile.
*/
public FileSystem(String bootFileName, PrintStream out, File dummyFile)
{
this.bootFileName = bootFileName;
this.out = out;
this.dummyFile = dummyFile;
bootDevices();
} //end constructor
/**
* Boot all devices that could be found at the bootFile.
* All devices that couldn't be initialized or found on the hard-disk will
* be skipped.
*/
private void bootDevices() {
try {
RandomAccessFile bootFile;
try {
bootFile = new RandomAccessFile(bootFileName, "rw");
}
catch (FileNotFoundException e) {
throw new InitializationException("The file '"+bootFileName+"' could not be found.");
}
long bflength = bootFile.length();
String line = "";
do {
line = bootFile.readLine();
if (line == null || line.equals(""))
return;
StringTokenizer stringTokenizer = new StringTokenizer(line, "\t");
String deviceName = stringTokenizer.nextToken();
long sizeInByts = (new Long( stringTokenizer.nextToken() )).longValue();
String rawType = stringTokenizer.nextToken();
RawAccess rawAccess = null;
try {
if (rawType.equals("RAF"))
rawAccess = new RAFRawAccess(deviceName);
else if (rawType.equals("RAM"))
rawAccess = new RAMRawAccess(MyMath.roundUp(sizeInByts/512.0));
else if (rawType.equals("NATIVE"))
rawAccess = new NativeRawAccess(deviceName);
else {
out.println("Wrong RawAccessType in file "+bootFileName+". It has to be: RAF, RAM, or NATIVE");
continue;
}
if (rawAccess == null)
throw new NullPointerException();
}
catch (Exception e) {
out.println("Couldn't not open the RawAccess file "+deviceName+". Skip the boot process of the associated device.");
continue;
}
try {
FATDevice device = new FATDevice(deviceName, rawAccess, out, dummyFile);
devices.add(new DeviceInformation(device));
}
catch (InitializationException e) {
out.println(e);
}
} while(!line.equals("") || bootFile.getFilePointer() < bflength);
bootFile.close();
}
catch(IOException e) {
throw new RuntimeException("Couldn't boot the devices from "+bootFileName);
}
}
/**
* Return a FATDevice which supports all file system operations on the raw device
* specified by device name. The I/O-opeartions are supported by the RawAccess file.
* If the requested device already exist no new device will be created. The method
* returns the existing device.
* For a FAT12 file system the length of rawAccess must be smaller than 4084 blocks.
* For a FAT16 file system the length of rawAccess must be smaller than 4194304
* 512-byte-blocks and bigger than 32680 512-byte-blocks.
* For a FAT32 file system the length of rawAccess must be smaller than 0xFFFFFFFF bytes.
* and bigger than 532480 512-byte-blocks.
* @param deviceName the name of the device.
* @param rawAccess the raw access file which supports the I/O-Operations.
* @param fatType the type of FAT that should be formatted. In case you boot the
* device you can set fatType as UNKNOWN. The device will recognize by itself
* what kind of FAT it is.
* @param actionType indicated the action to do, that are BOOT an existing device
* or FORMAT the partition whith the given fat type.
* @return the device object.
* @throws WrongFATType if the given fatType is neither FAT.FAT12, nor FAT.FAT16, nor FAT.FAT32.
* @throws WrongLength if the number of blocks of rawAccess are to big for the given fatType.
* @throws InitializationException in case the device object couldn't be initialized.
*/
public FATDevice initialize(String deviceName, RawAccess rawAccess, byte fatType, byte actionType)
throws WrongFATType, WrongLength, InitializationException
{
if (actionType == FORMAT && fatType != FAT.FAT12 && fatType != FAT.FAT16 && fatType != FAT.FAT32)
throw new WrongFATType(fatType);
Iterator it = devices.iterator();
DeviceInformation devInf = null;
FATDevice device = null;
while(it.hasNext())
{
devInf = (DeviceInformation)it.next();
device = devInf.getDevice();
if (device.getRealDeviceName().equals(deviceName))
{
devInf.addUser();
return device;
}
}
if (actionType == BOOT)
{
device = new FATDevice(deviceName, rawAccess,out, dummyFile);
devices.add(new DeviceInformation(device));
return device;
}
else if (actionType == FORMAT)
{
device = new FATDevice(deviceName, fatType, rawAccess, out, dummyFile);
//add the new device to the bootFile
String deviceType = null;
long sizeInByts = rawAccess.getNumSectors() * 512;
if (rawAccess instanceof RAFRawAccess)
deviceType = "RAF";
else if (rawAccess instanceof RAMRawAccess)
deviceType = "RAM";
else if (rawAccess instanceof NativeRawAccess)
deviceType = "NATIVE";
else
throw new InitializationException("Unknown RawAccess type. Device is not written to bootFile.");
if (deviceType != null && !deviceType.equals("RAM"))
{
RandomAccessFile bootFile;
try {
bootFile = new RandomAccessFile(bootFileName, "rw");
if (bootFile.length() > 0)
bootFile.seek(bootFile.length()-1);
bootFile.write(deviceName.getBytes("US-ASCII"));
bootFile.write(0x09); // "\t"
bootFile.write((new Long(sizeInByts)).toString().getBytes("US-ASCII"));
bootFile.write(0x09); // "\t"
bootFile.write(deviceType.getBytes("US-ASCII"));
//write cr and lf
bootFile.write(0x0D); //cr
bootFile.write(0x0A); //lf
bootFile.close();
}
catch (IOException e) {
throw new InitializationException("Could not write to "+bootFileName);
}
}
devices.add(new DeviceInformation(device));
return device;
}
return null;
} //end initialize(String deviceName, RawAccess rawAccess, byte fatType, byte actionType)
/**
* Return the device object given by deviceName.
* @param deviceName name of the device.
* @return the device object.
* @throws Exception in case the device could not be found.
*/
public FATDevice getDevice(String deviceName) throws Exception
{
Iterator it = devices.iterator();
DeviceInformation devInf = null;
while (it.hasNext())
{
devInf = (DeviceInformation)it.next();
if (devInf.getDevice().getRealDeviceName().equals(deviceName))
{
devInf.addUser();
return devInf.device;
}
}
throw new Exception();
} //end getDevice(String deviceName)
/**
* Remove and return the FATDevice with the given name from the
* devices list. If no such device exists or the device can not
* be removed (because an other uses uses the device) null is
* returned.
* @param deviceName name of the device.
* @return the FATDevice with the given name from the devices list.
*/
private FATDevice removeDevice(String deviceName)
{
Iterator it = devices.iterator();
FATDevice device = null;
DeviceInformation devInf = null;
while(it.hasNext())
{
devInf = (DeviceInformation)it.next();
device = devInf.getDevice();
if (devInf.getDevice().getRealDeviceName().equals(deviceName))
{
devInf.removeUser();
//check if the device can be removed
if (devInf.getNumOfUsers() > 0)
return null; //device can not be removed because there is at last one active user
it.remove();
return device;
}
}
return null;
} //end removeDevice(String deviceName)
/**
* Call this method when you finished your work with the file system. If
* you don't call it, the next time you boot the file system checkdisk
* will start and you may lose some important information.
*/
public void shutDown()
{
FATDevice device;
for (int i=0; i < devices.size(); i++)
{
device = ((DeviceInformation)devices.get(i)).getDevice();
if (device != null)
device.unmount();
}
devices.clear();
} //end shutDown()
/**
* Unmount the device given by deviceName.
* @param deviceName name of the device.
*/
public void unmount(String deviceName)
{
FATDevice device = removeDevice(deviceName);
if (device != null)
device.unmount();
} //end unmount(String deviceName)
/**
* Unmount the given device.
* @param device to unmount.
*/
public void unmount(FATDevice device)
{
unmount(device.getRealDeviceName());
}
/**
* List the available devices.
* Each device has a root directory from which all other files in that file system can be reached.
* This method returns a list of DeviceInformation objects which contains information about
* each device.
* @return list of DeviceInformation objects with all mounted devices.
* @see xxl.core.io.fat.FileSystem.DeviceInformation
*/
public List getAllDevices()
{
return devices;
} //end getAllDevices()
}