diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java new file mode 100644 index 0000000000..a23b40436d --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.smb.server.repo; + +import java.io.FileNotFoundException; + +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.filesys.DiskDeviceContext; +import org.alfresco.filesys.server.filesys.FileName; +import org.alfresco.filesys.server.filesys.IOControlNotImplementedException; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.filesys.server.filesys.NotifyChange; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.alfresco.filesys.smb.NTIOCtl; +import org.alfresco.filesys.smb.SMBException; +import org.alfresco.filesys.smb.SMBStatus; +import org.alfresco.filesys.smb.server.repo.CifsHelper; +import org.alfresco.filesys.smb.server.repo.ContentDiskDriver; +import org.alfresco.filesys.smb.server.repo.IOControlHandler; +import org.alfresco.filesys.util.DataBuffer; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Content Disk Driver I/O Control Handler Class + * + *
Provides the custom I/O control code handling used by the CIFS client interface application. + * + * @author gkspencer + */ +public class ContentIOControlHandler implements IOControlHandler +{ + // Logging + + private static final Log logger = LogFactory.getLog(ContentIOControlHandler.class); + + // Services and helpers + + private CifsHelper cifsHelper; + private TransactionService transactionService; + private NodeService nodeService; + private CheckOutCheckInService checkInOutService; + + private ContentDiskDriver contentDriver; + + /** + * Default constructor + */ + public ContentIOControlHandler() + { + } + + /** + * Initalize the I/O control handler + * + * @param contentDriver ContentDiskDriver + * @param cifsHelper CifsHelper + * @param transService TransactionService + * @param nodeService NodeService + * @param cociService CheckOutCheckInService + */ + public void initialize( ContentDiskDriver contentDriver, CifsHelper cifsHelper, + TransactionService transService, NodeService nodeService, CheckOutCheckInService cociService) + { + this.contentDriver = contentDriver; + this.cifsHelper = cifsHelper; + this.transactionService = transService; + this.nodeService = nodeService; + this.checkInOutService = cociService; + } + + /** + * Process a filesystem I/O control request + * + * @param sess Server session + * @param tree Tree connection. + * @param ctrlCode I/O control code + * @param fid File id + * @param dataBuf I/O control specific input data + * @param isFSCtrl true if this is a filesystem control, or false for a device control + * @param filter if bit0 is set indicates that the control applies to the share root handle + * @return DataBuffer + * @exception IOControlNotImplementedException + * @exception SMBException + */ + public DataBuffer processIOControl(SrvSession sess, TreeConnection tree, int ctrlCode, int fid, DataBuffer dataBuf, + boolean isFSCtrl, int filter) + throws IOControlNotImplementedException, SMBException + { + // Validate the file id + + NetworkFile netFile = tree.findFile(fid); + if ( netFile == null || netFile.isDirectory() == false) + throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); + + // Split the control code + + int devType = NTIOCtl.getDeviceType(ctrlCode); + int ioFunc = NTIOCtl.getFunctionCode(ctrlCode); + + if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null) + throw new IOControlNotImplementedException(); + + // Check if the request has a valid signature for an Alfresco CIFS server I/O control + + if ( dataBuf.getLength() < IOControl.Signature.length()) + throw new IOControlNotImplementedException("Bad request length"); + + String sig = dataBuf.getString(IOControl.Signature.length(), false); + + if ( sig == null || sig.compareTo(IOControl.Signature) != 0) + throw new IOControlNotImplementedException("Bad request signature"); + + // Get the node for the parent folder, make sure it is a folder + + NodeRef folderNode = null; + + try + { + folderNode = contentDriver.getNodeForPath(tree, netFile.getFullName()); + + if ( cifsHelper.isDirectory( folderNode) == false) + folderNode = null; + } + catch ( FileNotFoundException ex) + { + folderNode = null; + } + + // If the folder node is not valid return an error + + if ( folderNode == null) + throw new SMBException(SMBStatus.NTErr, SMBStatus.NTAccessDenied); + + // Debug + + if ( logger.isInfoEnabled()) { + logger.info("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); + logger.info(" Folder nodeRef=" + folderNode); + } + + // Check if the I/O control code is one of our custom codes + + DataBuffer retBuffer = null; + + switch ( ioFunc) + { + // Probe to check if this is an Alfresco CIFS server + + case IOControl.CmdProbe: + + // Return a buffer with the signature + + retBuffer = new DataBuffer(IOControl.Signature.length()); + retBuffer.putFixedString(IOControl.Signature, IOControl.Signature.length()); + retBuffer.putInt(IOControl.StsSuccess); + break; + + // Get file information for a file within the current folder + + case IOControl.CmdFileStatus: + + // Process the file status request + + retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode); + break; + + // Check-in file request + + case IOControl.CmdCheckIn: + + // Process the check-in request + + retBuffer = procIOCheckIn( sess, tree, dataBuf, folderNode, netFile); + break; + + // Check-out file request + + case IOControl.CmdCheckOut: + + // Process the check-out request + + retBuffer = procIOCheckOut( sess, tree, dataBuf, folderNode, netFile); + break; + + // Unknown I/O control code + + default: + throw new IOControlNotImplementedException(); + } + + // Return the reply buffer, may be null + + return retBuffer; + } + + /** + * Process the file status I/O request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @return DataBuffer + */ + private final DataBuffer procIOFileStatus( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode) + { + // Start a transaction + + sess.beginTransaction( transactionService, true); + + // Get the file name from the request + + String fName = reqBuf.getString( true); + logger.info(" File status, fname=" + fName); + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the node for the file/folder + + NodeRef childNode = null; + + try + { + childNode = cifsHelper.getNodeRef( folderNode, fName); + } + catch (FileNotFoundException ex) + { + } + + // Check if the file/folder was found + + if ( childNode == null) + { + // Return an error response + + respBuf.putInt(IOControl.StsFileNotFound); + return respBuf; + } + + // Check if this is a file or folder node + + if ( cifsHelper.isDirectory( childNode)) + { + // Only return the status and node type for folders + + respBuf.putInt(IOControl.StsSuccess); + respBuf.putInt(IOControl.TypeFolder); + } + else + { + // Indicate that this is a file node + + respBuf.putInt(IOControl.StsSuccess); + respBuf.putInt(IOControl.TypeFile); + + // Check if this file is a working copy + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) + { + // Indicate that this is a working copy + + respBuf.putInt(IOControl.True); + + // Get the owner username and file it was copied from + + String owner = (String) nodeService.getProperty( childNode, ContentModel.PROP_WORKING_COPY_OWNER); + String copiedFrom = null; + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_COPIEDFROM)) + { + // Get the path of the file the working copy was generated from + + NodeRef fromNode = (NodeRef) nodeService.getProperty( childNode, ContentModel.PROP_COPY_REFERENCE); + if ( fromNode != null) + copiedFrom = (String) nodeService.getProperty( fromNode, ContentModel.PROP_NAME); + } + + // Pack the owner and copied from values + + respBuf.putString(owner != null ? owner : "", true, true); + respBuf.putString(copiedFrom != null ? copiedFrom : "", true, true); + } + else + { + // Not a working copy + + respBuf.putInt(IOControl.False); + } + + // Check the lock status of the file + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_LOCKABLE)) + { + // Get the lock type and owner + + String lockTypeStr = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_TYPE); + String lockOwner = null; + + if ( lockTypeStr != null) + lockOwner = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_OWNER); + + // Pack the lock type, and owner if there is a lock on the file + + if ( lockTypeStr == null) + respBuf.putInt(IOControl.LockNone); + else + { + LockType lockType = LockType.valueOf( lockTypeStr); + + respBuf.putInt(lockType == LockType.READ_ONLY_LOCK ? IOControl.LockRead : IOControl.LockWrite); + respBuf.putString(lockOwner != null ? lockOwner : "", true, true); + } + } + else + { + // File is not lockable + + respBuf.putInt(IOControl.LockNone); + } + + // Get the content data details for the file + + ContentData contentData = (ContentData) nodeService.getProperty( childNode, ContentModel.PROP_CONTENT); + + if ( contentData != null) + { + // Get the content mime-type + + String mimeType = contentData.getMimetype(); + + // Pack the content length and mime-type + + respBuf.putInt( IOControl.True); + respBuf.putLong( contentData.getSize()); + respBuf.putString( mimeType != null ? mimeType : "", true, true); + } + else + { + // File does not have any content + + respBuf.putInt( IOControl.False); + } + } + + // Return the response + + return respBuf; + } + + /** + * Process the check in I/O request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procIOCheckIn( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // Start a transaction + + sess.beginTransaction( transactionService, false); + + // Get the file name from the request + + String fName = reqBuf.getString( true); + boolean keepCheckedOut = reqBuf.getInt() == IOControl.True ? true : false; + + logger.info(" CheckIn, fname=" + fName + ", keepCheckedOut=" + keepCheckedOut); + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the node for the file/folder + + NodeRef childNode = null; + + try + { + childNode = cifsHelper.getNodeRef( folderNode, fName); + } + catch (FileNotFoundException ex) + { + } + + // Check if the file/folder was found + + if ( childNode == null) + { + // Return an error response + + respBuf.putInt(IOControl.StsFileNotFound); + return respBuf; + } + + // Check if this is a file or folder node + + if ( cifsHelper.isDirectory( childNode)) + { + // Return an error status, attempt to check in a folder + + respBuf.putInt(IOControl.StsBadParameter); + } + else + { + // Check if this file is a working copy + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) + { + try + { + // Check in the file + + checkInOutService.checkin( childNode, null, null, keepCheckedOut); + + // Check in was successful + + respBuf.putInt( IOControl.StsSuccess); + + // Check if there are any file/directory change notify requests active + + DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); + if (diskCtx.hasChangeHandler()) { + + // Build the relative path to the checked in file + + String fileName = FileName.buildPath( netFile.getFullName(), null, fName, FileName.DOS_SEPERATOR); + + // Queue a file deleted change notification + + diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, fileName); + } + } + catch (Exception ex) + { + // Return an error status and message + + respBuf.setPosition( IOControl.Signature.length()); + respBuf.putInt(IOControl.StsError); + respBuf.putString( ex.getMessage(), true, true); + } + } + else + { + // Not a working copy + + respBuf.putInt(IOControl.StsNotWorkingCopy); + } + } + + // Return the response + + return respBuf; + } + + /** + * Process the check out I/O request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procIOCheckOut( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // Start a transaction + + sess.beginTransaction( transactionService, false); + + // Get the file name from the request + + String fName = reqBuf.getString( true); + + logger.info(" CheckOut, fname=" + fName); + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the node for the file/folder + + NodeRef childNode = null; + + try + { + childNode = cifsHelper.getNodeRef( folderNode, fName); + } + catch (FileNotFoundException ex) + { + } + + // Check if the file/folder was found + + if ( childNode == null) + { + // Return an error response + + respBuf.putInt(IOControl.StsFileNotFound); + return respBuf; + } + + // Check if this is a file or folder node + + if ( cifsHelper.isDirectory( childNode)) + { + // Return an error status, attempt to check in a folder + + respBuf.putInt(IOControl.StsBadParameter); + } + else + { + try + { + // Check out the file + + NodeRef workingCopyNode = checkInOutService.checkout( childNode); + + // Get the working copy file name + + String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME); + + // Check out was successful, pack the working copy name + + respBuf.putInt( IOControl.StsSuccess); + respBuf.putString( workingCopyName, true, true); + + // Check if there are any file/directory change notify requests active + + DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); + if (diskCtx.hasChangeHandler()) { + + // Build the relative path to the checked in file + + String fileName = FileName.buildPath( netFile.getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR); + + // Queue a file added change notification + + diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, fileName); + } + } + catch (Exception ex) + { + // Return an error status and message + + respBuf.setPosition( IOControl.Signature.length()); + respBuf.putInt(IOControl.StsError); + respBuf.putString( ex.getMessage(), true, true); + } + } + + // Return the response + + return respBuf; + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/IOControl.java b/source/java/org/alfresco/filesys/smb/server/repo/IOControl.java new file mode 100644 index 0000000000..e74078344c --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/IOControl.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.smb.server.repo; + +import org.alfresco.filesys.smb.NTIOCtl; + +/** + * Content Disk Driver I/O Control Codes Class + * + *
contains I/O control codes and status codes used by the content disk driver I/O control + * implementation. + * + * @author gkspencer + */ +public class IOControl +{ + // Custom I/O control codes + + public static final int CmdProbe = NTIOCtl.FsCtlCustom; + public static final int CmdFileStatus = NTIOCtl.FsCtlCustom + 1; + public static final int CmdCheckOut = NTIOCtl.FsCtlCustom + 2; + public static final int CmdCheckIn = NTIOCtl.FsCtlCustom + 3; + + // I/O control request/response signature + + public static final String Signature = "ALFRESCO"; + + // I/O control status codes + + public static final int StsSuccess = 0; + + public static final int StsError = 1; + public static final int StsFileNotFound = 2; + public static final int StsAccessDenied = 3; + public static final int StsBadParameter = 4; + public static final int StsNotWorkingCopy = 5; + + // Boolean field values + + public static final int True = 1; + public static final int False = 0; + + // File status field values + // + // Node type + + public static final int TypeFile = 0; + public static final int TypeFolder = 1; + + // Lock status + + public static final int LockNone = 0; + public static final int LockRead = 1; + public static final int LockWrite = 2; +}