package io.eguan.iscsisrv; /* * #%L * Project eguan * %% * Copyright (C) 2012 - 2017 Oodrive * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import io.eguan.srv.DeviceTarget; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import org.jscsi.target.Target; import org.jscsi.target.storage.IStorageModule; /** * This class represents a iSCSI target. The target has a name, an optional alias and is associated to a device. * * @author oodrive * @author llambert * @author ebredzinski */ @Immutable public final class IscsiTarget implements DeviceTarget { /** Associated jSCSI target */ private final Target target; /** * Create a new target. * * @param target */ private IscsiTarget(@Nonnull final Target target) { super(); this.target = target; } /** * Gets the name of the target. * * @return the name of the target */ @Override public final String getTargetName() { return target.getTargetName(); } /** * Gets the target alias. * * @return the target alias. Maybe <code>null</code> */ public final String getTargetAlias() { return target.getTargetAlias(); } /** * Tells if the target is read only * * @return <code>true</code> if the target is read only. */ public final boolean isReadOnly() { return target.getStorageModule().isWriteProtected(); } /** * Gets the size of the device in bytes. Same value as the jSCSI server: rounded to the smaller block size. * * @return the size of the device in bytes. */ final long getSize() { final IStorageModule device = target.getStorageModule(); return device.getSizeInBlocks() * target.getStorageModule().getBlockSize(); } /** * Close the device of the target. * * @throws IOException */ final void close() throws IOException { final IStorageModule device = target.getStorageModule(); device.close(); } /** * Create a new target with the current name. * * @param name * iSCSI target name * @param alias * iSCSI target alias. May be <code>null</code> * @param device * opened device for the new target. * @return a new {@link IscsiTarget} based on the given device. */ public static final IscsiTarget newIscsiTarget(@Nonnull final String name, @Nullable final String alias, @Nonnull final IscsiDevice device) { final IStorageModule storageModule = new IStorageModuleImpl(device); final Target target = new Target(checkTargetName(name), checkTargetAlias(alias), storageModule); return new IscsiTarget(target); } /** * Checks if the target name is a valid iSCSI target name (see RFC 3720). * * @param name * @return name * @throws IllegalArgumentException */ private static final String checkTargetName(@Nonnull final String name) throws IllegalArgumentException { // Not null Objects.requireNonNull(name); if (name.startsWith("iqn.")) { // Max length in bytes: 233, including the trailing 0 checkUTF8Length(name, 233, "name="); // TODO check format: iqn.<date>.<domain>:<string> } else if (name.startsWith("eui.")) { // 16 ASCII-encoded hexadecimal digits if (name.length() != 20) { throw new IllegalArgumentException("name=" + name); } if (!name.substring(4).matches("\\p{XDigit}+")) { throw new IllegalArgumentException("name=" + name); } } else { throw new IllegalArgumentException("name=" + name); } return name; } /** * Checks if the target alias is a valid iSCSI target alias (see RFC 3721). * * @param alias * @return alias * @throws IllegalArgumentException */ private static final String checkTargetAlias(@Nullable final String alias) throws IllegalArgumentException { // May be null if (alias == null) { return null; } // Max length in bytes: 255, including the trailing 0 checkUTF8Length(alias, 255, "alias="); return alias; } /** * Checks the length of a string, coded in UTF-8 format. * * @param str * @param maxLength * max length, including a trailing 0 * @param mgsHeader * header of the message of the exception * @throws IllegalArgumentException */ private static final void checkUTF8Length(@Nonnull final String str, final int maxLength, final String mgsHeader) throws IllegalArgumentException { try { final byte[] bytes = str.getBytes("UTF-8"); if (bytes.length > maxLength || (bytes.length == maxLength && bytes[maxLength - 1] != 0)) { throw new IllegalArgumentException(mgsHeader + str); } } catch (final UnsupportedEncodingException e) { // a JVM must support utf-8 throw new AssertionError("UTF-8 unsupported", e); } } /** * Needed by the server. * * @return the jSCSI target. */ final Target getTarget() { return target; } /** * The comparison is based only on the name of the target. * * @see java.lang.Object#equals(java.lang.Object) */ @Override public final boolean equals(final Object obj) { if (this == obj) return true; if (!(obj instanceof IscsiTarget)) return false; final IscsiTarget other = (IscsiTarget) obj; return target.equals(other.target); } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public final int hashCode() { final int prime = 31; int result = 1; result = prime * result + target.hashCode(); return result; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public final String toString() { return "IscsiTarget[" + target.getTargetName() + "]"; } }