package org.jscsi.target; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.jscsi.target.connection.Connection; import org.jscsi.target.scsi.lun.LogicalUnitNumber; import org.jscsi.target.settings.TextKeyword; import org.jscsi.target.storage.IStorageModule; import org.jscsi.target.storage.JCloudsStorageModule; import org.jscsi.target.storage.RandomAccessStorageModule; import org.jscsi.target.storage.SynchronizedRandomAccessStorageModule; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.SAXException; /** * Instances of {@link Configuration} provides access target-wide parameters, * variables that are the same across all sessions and connections that do not * change after initialization and which play a role during text parameter * negotiation. Some of these parameters are provided or can be overridden by * the content of an XML file - <code>jscsi-target.xml</code>. * * @author Andreas Ergenzinger, University of Konstanz */ public class Configuration { public static final String ELEMENT_TARGET_LIST = "TargetList"; // Name of // node that // contains // list of // targets public static final String ELEMENT_TARGET = "Target"; // Name for nodes // that contain a // target // Target configuration elements public static final String ELEMENT_SYNCFILESTORAGE = "SyncFileStorage"; public static final String ELEMENT_ASYNCFILESTORAGE = "AsyncFileStorage"; public static final String ELEMENT_JCLOUDSSTORAGE = "JCloudsStorage"; public static final String ELEMENT_CREATE = "Create"; public static final String ATTRIBUTE_SIZE = "size"; // Global configuration elements public static final String ELEMENT_ALLOWSLOPPYNEGOTIATION = "AllowSloppyNegotiation"; public static final String ELEMENT_PORT = "Port"; // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * The relative path (to the project) of the main directory of all * configuration files. */ private static final File CONFIG_DIR = new File(new StringBuilder("src").append(File.separator).append( "main").append(File.separator).append("resources").append(File.separator).toString()); /** * The file name of the XML Schema configuration file for the global * settings. */ public static final File CONFIGURATION_SCHEMA_FILE = new File(CONFIG_DIR, "jscsi-target.xsd"); /** The file name, which contains all global settings. */ public static final File CONFIGURATION_CONFIG_FILE = new File(CONFIG_DIR, "jscsi-target.xml"); // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- protected final List<Target> targets; /** * The <code>TargetAddress</code> parameter (the jSCSI Target's IP address). * <p> * This parameter is initialized automatically. */ protected String targetAddress; private final boolean targetAddressIsAnyLocal; private final String localHostAddress; /** * The port used by the jSCSI Target for listening for new connections. * <p> * The default port number is 3260. This value may be overridden by specifying a different value in the * configuration file. */ protected int port; /** * This variable toggles the strictness with which the parameters <code>IFMarkInt</code> and * <code>OFMarkInt</code> are processed, when * provided by the initiator. Usually the offered values must have to * following format: <code>smallInteger~largeInteger</code>, however the * jSCSI Initiator sends only single integers as <i>value</i> part of the * <i>key-value</i> pairs. Since the value of these two parameters always * are <code>Irrelevant</code>, this bug can be ignored without any negative * consequences by setting {@link #allowSloppyNegotiation} to <code>true</code> in the configuration file. * The default is <code>false</code>. */ protected boolean allowSloppyNegotiation;// TODO fix in jSCSI Initiator and // remove /** * The <code>TargetPortalGroupTag</code> parameter. */ protected final int targetPortalGroupTag = 1; /** * The Logical Unit Number of the virtual Logical Unit. */ protected final LogicalUnitNumber logicalUnitNumber = new LogicalUnitNumber(0L); /** * The <code>MaxRecvDataSegmentLength</code> parameter for PDUs sent in the * out direction (i.e. initiator to target). * <p> * Since the value of this variable is equal to the specified default value, it does not have to be * declared during login. */ protected final int outMaxRecvDataSegmentLength = 8192; /** * The maximum number of consecutive Login PDUs or Text Negotiation PDUs the * target will accept in a single sequence. * <p> * The iSCSI standard does not dictate a minimum or maximum text PDU sequence length, but only suggests to * select a value large enough for all expected key-value pairs that might be sent in a single sequence. A * limit should be imposed, however, to prevent {@link OutOfMemoryError}s resulting from malicious or * accidental text PDU sequences of extreme lengths. * <p> * Since all common text parameters (plus values) easily fit into a single text PDU with the default data * segment size, this value could be set to <code>1</code> without negatively affecting compatibility with * most initiators. */ private final int maxRecvTextPduSequenceLength = 4; public Configuration() throws IOException { port = 3260; final InetAddress localhost = InetAddress.getLocalHost(); targetAddress = localhost.getHostAddress(); targetAddressIsAnyLocal = localhost.isAnyLocalAddress(); localHostAddress = targetAddress; targets = new ArrayList<Target>(); } // OODRIVE: new constructor to set port and address without exception; empty target list public Configuration(int port, InetAddress address) { this.port = port; this.targetAddress = address.getHostAddress(); this.targetAddressIsAnyLocal = address.isAnyLocalAddress(); try { this.localHostAddress = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { // Some error with the network configuration throw new IllegalStateException(e); } this.targets = new ArrayList<Target>(0); } public int getInMaxRecvTextPduSequenceLength() { return maxRecvTextPduSequenceLength; } public int getOutMaxRecvDataSegmentLength() { return outMaxRecvDataSegmentLength; } public String getTargetAddress() { return targetAddress; } public String getTargetAddress(Connection connection) { if (targetAddressIsAnyLocal) { // Get local address for the connection final String localAddr = connection.getLocalAddress(); if (localAddr == null) { return localHostAddress; // default local address } return localAddr; } return targetAddress; } // OODRIVE add getter for InetAddress (no exception) public InetAddress getTargetAddressInetAddress() { try { return InetAddress.getByName(targetAddress); } catch (UnknownHostException e) { throw new IllegalStateException(e); } } public int getPort() { return port; } public boolean getAllowSloppyNegotiation() { return allowSloppyNegotiation; } public int getTargetPortalGroupTag() { return targetPortalGroupTag; } public LogicalUnitNumber getLogicalUnitNumber() { return logicalUnitNumber; } public List<Target> getTargets() { return targets; } public static Configuration create() throws SAXException, ParserConfigurationException, IOException { return create(CONFIGURATION_SCHEMA_FILE, CONFIGURATION_CONFIG_FILE); } /** * Reads the given configuration file in memory and creates a DOM * representation. * * @throws SAXException * If this operation is supported but failed for some reason. * @throws ParserConfigurationException * If a {@link DocumentBuilder} cannot be created which * satisfies the configuration requested. * @throws IOException * If any IO errors occur. */ public static Configuration create(final File schemaLocation, final File configFile) throws SAXException, ParserConfigurationException, IOException { final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); final Schema schema = schemaFactory.newSchema(schemaLocation); // create a validator for the document final Validator validator = schema.newValidator(); final DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); domFactory.setNamespaceAware(true); // never forget this final DocumentBuilder builder = domFactory.newDocumentBuilder(); final Document doc = builder.parse(configFile); final DOMSource source = new DOMSource(doc); final DOMResult result = new DOMResult(); validator.validate(source, result); Document root = (Document)result.getNode(); // TargetName Configuration returnConfiguration = new Configuration(); Element targetListNode = (Element)root.getElementsByTagName(ELEMENT_TARGET_LIST).item(0); NodeList targetList = targetListNode.getElementsByTagName(ELEMENT_TARGET); for (int curTargetNum = 0; curTargetNum < targetList.getLength(); curTargetNum++) { Target curTargetInfo = parseTargetElement((Element)targetList.item(curTargetNum)); synchronized (returnConfiguration.targets) { returnConfiguration.targets.add(curTargetInfo); } } // else it is null // port if (root.getElementsByTagName(ELEMENT_PORT).getLength() > 0) returnConfiguration.port = Integer.parseInt(root.getElementsByTagName(ELEMENT_PORT).item(0).getTextContent()); else returnConfiguration.port = 3260; // support sloppy text parameter negotiation (i.e. the jSCSI Initiator)? final Node allowSloppyNegotiationNode = root.getElementsByTagName(ELEMENT_ALLOWSLOPPYNEGOTIATION).item(0); if (allowSloppyNegotiationNode == null) returnConfiguration.allowSloppyNegotiation = false; else returnConfiguration.allowSloppyNegotiation = Boolean.parseBoolean(allowSloppyNegotiationNode.getTextContent()); return returnConfiguration; } protected static Target parseTargetElement(Element targetElement) throws IOException { // TargetName // TargetName Node nextNode = chopWhiteSpaces(targetElement.getFirstChild()); // assert // nextNode.getLocalName().equals(OperationalTextKey.TARGET_NAME); String targetName = nextNode.getTextContent(); // TargetAlias (optional) nextNode = chopWhiteSpaces(nextNode.getNextSibling()); String targetAlias = ""; if (nextNode.getLocalName().equals(TextKeyword.TARGET_ALIAS)) { targetAlias = nextNode.getTextContent(); nextNode = chopWhiteSpaces(nextNode.getNextSibling()); } // Finding out the concrete storage Class<? extends IStorageModule> kind = null; switch (nextNode.getLocalName()) { case ELEMENT_SYNCFILESTORAGE: kind = SynchronizedRandomAccessStorageModule.class; break; case ELEMENT_ASYNCFILESTORAGE: kind = RandomAccessStorageModule.class; break; case ELEMENT_JCLOUDSSTORAGE: kind = JCloudsStorageModule.class; break; } // Getting storagepath nextNode = nextNode.getFirstChild(); nextNode = chopWhiteSpaces(nextNode); // assert nextNode.getLocalName().equals(ELEMENT_PATH); String storageFilePath = nextNode.getTextContent(); // CreateNode with size nextNode = chopWhiteSpaces(nextNode.getNextSibling()); long storageLength = -1; boolean create = true; if (nextNode.getLocalName().equals(ELEMENT_CREATE)) { Node sizeAttribute = nextNode.getAttributes().getNamedItem(ATTRIBUTE_SIZE); storageLength = Math.round(((Double.valueOf(sizeAttribute.getTextContent())) * Math.pow(1024, 3))); } else { storageLength = new File(storageFilePath).length(); create = false; // assert nextNode.getLocalName().equals(ELEMENT_DONTCREATE); } final IStorageModule module = RandomAccessStorageModule.open(new File(storageFilePath), storageLength, create, kind); return new Target(targetName, targetAlias, module); } protected static Node chopWhiteSpaces(final Node node) { Node toIterate = node; while (toIterate instanceof Text && toIterate.getTextContent().trim().length() == 0) { toIterate = toIterate.getNextSibling(); } return toIterate; } }