// $Id: DirectoryLookUpPool.java,v 1.7 2007-07-26 14:34:12 tigran Exp $ package diskCacheV111.pools; import com.google.common.collect.Range; import dmg.util.command.Argument; import dmg.util.command.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.Subject; import java.io.DataInputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.Socket; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.Callable; import diskCacheV111.util.CacheException; import diskCacheV111.util.FileNotFoundCacheException; import diskCacheV111.util.FsPath; import diskCacheV111.util.NotDirCacheException; import diskCacheV111.util.PnfsHandler; import diskCacheV111.util.PnfsId; import diskCacheV111.vehicles.DCapProtocolInfo; import diskCacheV111.vehicles.DirRequestMessage; import dmg.cells.nucleus.CellMessage; import dmg.cells.nucleus.CellPath; import dmg.cells.nucleus.DelayedReply; import dmg.cells.nucleus.NoRouteToCellException; import org.dcache.auth.Subjects; import org.dcache.auth.attributes.Restriction; import org.dcache.auth.attributes.Restrictions; import org.dcache.cells.AbstractCell; import org.dcache.util.Option; import org.dcache.namespace.FileAttribute; import org.dcache.pool.movers.DCapConstants; import org.dcache.pool.movers.DCapDataOutputStream; import org.dcache.util.Args; import org.dcache.util.list.DirectoryEntry; import org.dcache.util.list.DirectoryListPrinter; import org.dcache.util.list.DirectoryListSource; import org.dcache.util.list.ListDirectoryHandler; import org.dcache.vehicles.FileAttributes; import static org.dcache.namespace.FileAttribute.*; import static org.dcache.namespace.FileType.REGULAR; /** * Provides directory listing services for DCAP. */ public class DirectoryLookUpPool extends AbstractCell { private static final Logger _log = LoggerFactory.getLogger(DirectoryLookUpPool.class); private final String _poolName; private final Args _args; private PnfsHandler _pnfs; private DirectoryListSource _list; @Option(name = "pnfsManager", description = "Cell address of the PNFS manager", defaultValue = "PnfsManager") protected CellPath _pnfsManager; public DirectoryLookUpPool(String poolName, String args) { super(poolName, args); _poolName = poolName; _args = getArgs(); } @Override protected void starting() throws Exception { _log.info("Lookup Pool {} starting", _poolName); super.starting(); _pnfs = new PnfsHandler(this, _pnfsManager); ListDirectoryHandler listHandler = new ListDirectoryHandler(_pnfs); addMessageListener(listHandler); _list = listHandler; useInterpreter(true); } @Override public void getInfo(PrintWriter pw) { pw.println("Revision : [$Id: DirectoryLookUpPool.java,v 1.7 2007-07-26 14:34:12 tigran Exp $]"); } @Override public void messageToForward(CellMessage cellMessage) { messageArrived(cellMessage); } /** * List a directory. */ private String list(FsPath path, Subject subject, Restriction restriction) throws InterruptedException, CacheException { StringBuilder sb = new StringBuilder(); try { _list.printDirectory(subject, restriction, new DirectoryPrinter(sb), path, null, Range.<Integer>all()); } catch (FileNotFoundCacheException e) { sb.append("Path ").append(path).append(" does not exist."); } catch (NotDirCacheException e) { _list.printFile(subject, restriction, new FilePrinter(sb), path); } return sb.toString(); } /** * Reply format for directory listings. */ class DirectoryPrinter implements DirectoryListPrinter { private final StringBuilder _out; public DirectoryPrinter(StringBuilder out) { _out = out; } @Override public Set<FileAttribute> getRequiredAttributes() { return EnumSet.of(PNFSID, TYPE, SIZE); } @Override public void print(FsPath dir, FileAttributes dirAttr, DirectoryEntry entry) { FileAttributes attr = entry.getFileAttributes(); _out.append(attr.getPnfsId()); switch (attr.getFileType()) { case DIR: _out.append(":d:"); break; case REGULAR: case LINK: case SPECIAL: _out.append(":f:"); break; } _out.append(attr.getSizeIfPresent().or(0L)).append(':').append(entry.getName()); _out.append('\n'); } } /** * Reply format for single file listings. */ class FilePrinter implements DirectoryListPrinter { private final StringBuilder _out; public FilePrinter(StringBuilder out) { _out = out; } @Override public Set<FileAttribute> getRequiredAttributes() { return EnumSet.of(TYPE, SIZE); } @Override public void print(FsPath dir, FileAttributes dirAttr, DirectoryEntry entry) { FileAttributes attr = entry.getFileAttributes(); if (attr.getFileType() == REGULAR) { _out.append(dir.child(entry.getName())); _out.append(" : ").append(attr.getSizeIfPresent().or(0L)); } } } /** * List task that can serve as a DelayedReply. */ class ListThread extends DelayedReply implements Runnable { private final FsPath _path; public ListThread(FsPath path) { _path = path; } @Override public void run() { try { try { reply(list(_path, Subjects.ROOT, Restrictions.none())); } catch (CacheException e) { reply(e); } } catch (InterruptedException e) { // end of thread } } } // commands @Command(name = "ls", hint = "list the contents of a directory", description = "Get the list of all the contents in the specified directory path. " + "The directory listing is displayed in this format:\n" + "\t\t<Pnfs-ID>:<TYPE>:<SIZE>:<NAME>\n" + "where" + "\n\tPnfs-ID: is a unique identifier of the file;" + "\n\tTYPE: file type has two denotations, 'd' denotes directory and 'f' " + "for other file type (like regular, link or special);" + "\n\tSIZE: the size of the file in bytes; and" + "\n\tNAME: the name of the file.") public class LsCommand implements Callable<DelayedReply> { @Argument(metaVar = "directory", usage = "An absolute directory path.") FsPath path; @Override public DelayedReply call() throws InterruptedException, CacheException { ListThread thread = new ListThread(path); new Thread(thread, "list[" + path + "]").start(); return thread; } } // ////////////////////////////////////////////////////////////// // // The io File Part // // public DirRequestMessage messageArrived(DirRequestMessage message) { DCapProtocolInfo dcap = (DCapProtocolInfo) message.getProtocolInfo(); PnfsId pnfsId = message.getPnfsId(); Restriction restriction = message.getRestriction(); Subject subject = message.getSubject(); DirectoryService service = new DirectoryService(subject, restriction, dcap, pnfsId); new Thread(service, "list[" + pnfsId + "]").start(); message.setSucceeded(); return message; } public void messageArrived(NoRouteToCellException e) { _log.warn(e.getMessage()); } /** * A task which handles directory listing for a particular * client. We have an instance per client request. */ private class DirectoryService implements Runnable { private final DCapProtocolInfo dcap; private final PnfsId pnfsId; private final int sessionId; private final Subject subject; private final Restriction restriction; private DCapDataOutputStream ostream; private DataInputStream istream; private Socket dataSocket; private DCapDataOutputStream cntOut; private DataInputStream cntIn; DirectoryService(Subject subject, Restriction restriction, DCapProtocolInfo dcap, PnfsId pnfsId) { this.dcap = dcap; this.pnfsId = pnfsId; this.sessionId = dcap.getSessionId(); this.subject = subject; this.restriction = restriction; } @Override public void run() { boolean done = false; int commandSize; int commandCode; int minSize; int index = 0; try { FsPath path = _pnfs.getPathByPnfsId(pnfsId); String dirList = list(path, subject, restriction); connectToClinet(); while (!done && !Thread.currentThread().isInterrupted()) { commandSize = cntIn.readInt(); if (commandSize < 4) { throw new CacheException(44, "Protocol Violation (cl<4)"); } commandCode = cntIn.readInt(); switch (commandCode) { // ------------------------------------------------------------- // // The IOCMD_CLOSE // case DCapConstants.IOCMD_CLOSE: cntOut.writeACK(DCapConstants.IOCMD_CLOSE); done = true; break; // ------------------------------------------------------------- // // The ReadDir // case DCapConstants.IOCMD_READ: // // minSize = 12; if (commandSize < minSize) { throw new CacheException(45, "Protocol Violation (clREAD<8)"); } long numberOfEntries = cntIn.readLong(); _log.debug("requested " + numberOfEntries + " bytes"); cntOut.writeACK(DCapConstants.IOCMD_READ); index += doReadDir(cntOut, ostream, dirList, index, numberOfEntries); cntOut.writeFIN(DCapConstants.IOCMD_READ); break; default: cntOut.writeACK(1717, 9, "Invalid mover command : " + commandCode); break; } } } catch (CacheException e) { _log.error(e.toString()); } catch (IOException e) { _log.warn(e.toString()); } catch (InterruptedException e) { // end of thread } finally { if (ostream != null) { try { ostream.close(); } catch (IOException e) { _log.warn(e.toString()); } } if (istream != null) { try { istream.close(); } catch (IOException e) { _log.warn(e.toString()); } } if (dataSocket != null) { try { dataSocket.close(); } catch (IOException e) { _log.warn(e.toString()); } } } } void connectToClinet() throws IOException { dataSocket = new Socket(dcap.getSocketAddress().getAddress(), dcap.getSocketAddress().getPort()); ostream = new DCapDataOutputStream(dataSocket.getOutputStream()); istream = new DataInputStream(dataSocket.getInputStream()); _log.info("Connected to {}", dcap.getSocketAddress()); // // send the sessionId and our (for now) 0 byte security // challenge. // cntOut = ostream; cntIn = istream; cntOut.writeInt(sessionId); cntOut.writeInt(0); cntOut.flush(); } private int doReadDir(DCapDataOutputStream cntOut, DCapDataOutputStream ostream, String dirList, int index, long len) throws IOException { long rc; byte data[]; if (index > dirList.length()) { throw new ArrayIndexOutOfBoundsException("requested index greater then directory size"); } data = dirList.getBytes(); rc = len > dirList.length() - index ? dirList.length() - index : len; cntOut.writeDATA_HEADER(); ostream.writeDATA_BLOCK(data, index, (int) rc); ostream.writeDATA_TRAILER(); return (int) rc; } } // end of private class } // end of MultiProtocolPool