/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.server.filesys.db; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.locking.LockConflictException; import org.alfresco.jlan.server.SrvSession; import org.alfresco.jlan.server.core.DeviceContext; import org.alfresco.jlan.server.core.DeviceContextException; import org.alfresco.jlan.server.filesys.AccessDeniedException; import org.alfresco.jlan.server.filesys.DiskDeviceContext; import org.alfresco.jlan.server.filesys.DiskFullException; import org.alfresco.jlan.server.filesys.DiskInterface; import org.alfresco.jlan.server.filesys.DiskOfflineException; import org.alfresco.jlan.server.filesys.DiskSizeInterface; import org.alfresco.jlan.server.filesys.DiskVolumeInterface; import org.alfresco.jlan.server.filesys.FileAttribute; import org.alfresco.jlan.server.filesys.FileExistsException; import org.alfresco.jlan.server.filesys.FileIdInterface; import org.alfresco.jlan.server.filesys.FileInfo; import org.alfresco.jlan.server.filesys.FileName; import org.alfresco.jlan.server.filesys.FileNameException; import org.alfresco.jlan.server.filesys.FileOpenParams; import org.alfresco.jlan.server.filesys.FileSharingException; import org.alfresco.jlan.server.filesys.FileStatus; import org.alfresco.jlan.server.filesys.FileType; import org.alfresco.jlan.server.filesys.NetworkFile; import org.alfresco.jlan.server.filesys.SearchContext; import org.alfresco.jlan.server.filesys.SrvDiskInfo; import org.alfresco.jlan.server.filesys.SymbolicLinkInterface; import org.alfresco.jlan.server.filesys.TreeConnection; import org.alfresco.jlan.server.filesys.VolumeInfo; import org.alfresco.jlan.server.filesys.cache.FileState; import org.alfresco.jlan.server.filesys.cache.FileStateCache; import org.alfresco.jlan.server.filesys.cache.FileStateLockManager; import org.alfresco.jlan.server.filesys.loader.NamedFileLoader; import org.alfresco.jlan.server.filesys.quota.QuotaManager; import org.alfresco.jlan.server.locking.FileLockingInterface; import org.alfresco.jlan.server.locking.LockManager; import org.alfresco.jlan.smb.SharingMode; import org.alfresco.jlan.smb.WinNT; import org.alfresco.jlan.smb.server.ntfs.NTFSStreamsInterface; import org.alfresco.jlan.smb.server.ntfs.StreamInfo; import org.alfresco.jlan.smb.server.ntfs.StreamInfoList; import org.alfresco.jlan.util.WildCard; import org.springframework.extensions.config.ConfigElement; /** * Database Disk Driver Class * * @author gkspencer */ public class DBDiskDriver implements DiskInterface, DiskSizeInterface, DiskVolumeInterface, NTFSStreamsInterface, FileLockingInterface, FileIdInterface, SymbolicLinkInterface { // Attributes attached to the file state public static final String DBStreamList = "DBStreamList"; // Default mode values for files/folders, if not specified in the file/folder create parameters public static final int DefaultNFSFileMode = 0644; public static final int DefaultNFSDirMode = 0755; // Maximum file name length public static final int MaxFileNameLen = 255; // Lock manager private static LockManager _lockManager = new FileStateLockManager(); // Enable/disable debug output private boolean m_debug = false; /** * Close the specified file * * @param sess Session details * @param tree Tree connection * @param file Network file details * @exception IOException */ public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException { // Access the database context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the file is an NTFS stream if ( file.isStream()) { // Close the NTFS stream closeStream(sess, tree, file); // Check if the stream is marked for deletion if ( file.hasDeleteOnClose()) deleteStream(sess, tree, file.getFullNameStream()); return; } // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB closeFile() file=" + file.getFullName()); // Close the file dbCtx.getFileLoader().closeFile(sess, file); // Access the JDBC file DBNetworkFile jdbcFile = null; if ( file instanceof DBNetworkFile) { // Access the JDBC file jdbcFile = (DBNetworkFile) file; // Decrement the open file count FileState fstate = jdbcFile.getFileState(); // Check if the file state is valid, if not then check the main file state cache if ( fstate == null) { // Check the main file state cache fstate = getFileState(file.getFullName(), dbCtx, false); } else { // Decrement the open file count for this file fstate.decrementOpenCount(); } // Release any locks on the file owned by this session if ( jdbcFile.hasLocks()) { // Get the lock manager FileLockingInterface flIface = (FileLockingInterface) this; LockManager lockMgr = flIface.getLockManager(sess, tree); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("Releasing locks for closed file, file=" + jdbcFile.getFullName() + ", locks=" + jdbcFile.numberOfLocks()); // Release all locks on the file owned by this session lockMgr.releaseLocksForFile(sess, tree, file); } // Check if we have a valid file state if ( fstate != null) { // Update the cached file size DBFileInfo finfo = (DBFileInfo) fstate.findAttribute(FileState.FileInformation); if ( finfo != null && file.getWriteCount() > 0) { // Update the file size finfo.setSize(jdbcFile.getFileSize()); // Update the modified date/time finfo.setModifyDateTime(jdbcFile.getModifyDate()); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println(" File size=" + jdbcFile.getFileSize() + ", modifyDate=" + jdbcFile.getModifyDate()); } // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println(" Open count=" + jdbcFile.getFileState().getOpenCount()); } // Check if the file/directory is marked for delete if ( file.hasDeleteOnClose()) { // Check for a file or directory if ( file.isDirectory()) deleteDirectory(sess, tree, file.getFullName()); else deleteFile(sess, tree, file.getFullName()); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println(" Marked for delete"); } } else if ( Debug.EnableError) Debug.println("closeFile() Not DBNetworkFile file=" + file); // Check if the file was opened for write access, if so then update the file size and modify date/time if ( file.getGrantedAccess() != NetworkFile.READONLY && file.isDirectory() == false && file.getWriteCount() > 0) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println(" Update file size=" + file.getFileSize()); // Get the current date/time long modifiedTime = 0L; if ( file.hasModifyDate()) modifiedTime = file.getModifyDate(); else modifiedTime = System.currentTimeMillis(); // Check if the modified time is earlier than the file creation date/time if ( file.hasCreationDate() && modifiedTime < file.getCreationDate()) { // Use the creation date/time for the modified date/time modifiedTime = file.getCreationDate(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("Close file using creation date/time for modified date/time"); } // Update the file details try { // Update the file details FileInfo finfo = new FileInfo(); finfo.setFileSize( file.getFileSize()); finfo.setModifyDateTime(modifiedTime); finfo.setFileInformationFlags(FileInfo.SetFileSize + FileInfo.SetModifyDate); // Call the database interface dbCtx.getDBInterface().setFileInformation(file.getDirectoryId(), file.getFileId(), finfo); } catch (DBException ex) { } } } /** * Create a new directory * * @param sess Session details * @param tree Tree connection * @param params Directory create parameters * @exception IOException */ public void createDirectory(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException { // Access the database context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Get, or create, a file state for the new path. Initially this will indicate that the directory // does not exist. FileState fstate = getFileState(params.getPath(), dbCtx, false); if ( fstate != null && fstate.fileExists() == true) throw new FileExistsException("Path " + params.getPath() + " exists"); // If there is no file state check if the directory exists if ( fstate == null) { // Create a file state for the new directory fstate = getFileState(params.getPath(), dbCtx, true); // Get the file details for the directory if ( getFileDetails(params.getPath(), dbCtx, fstate) != null) throw new FileExistsException("Path " + params.getPath() + " exists"); } // Find the parent directory id for the new directory int dirId = findParentDirectoryId(dbCtx,params.getPath(),true); if ( dirId == -1) throw new IOException("Cannot find parent directory"); // Create the new directory entry try { // Get the directory name String[] paths = FileName.splitPath(params.getPath()); String dname = paths[1]; // Check if the directory name is too long if ( dname != null && dname.length() > MaxFileNameLen) throw new FileNameException("Directory name too long, " + dname); // If retention is enabled check if the file is a temporary folder boolean retain = true; if ( dbCtx.hasRetentionPeriod()) { // Check if the file is marked delete on close if ( params.isDeleteOnClose()) retain = false; } // Set the default NFS file mode, if not set if ( params.hasMode() == false) params.setMode(DefaultNFSDirMode); // Make sure the create directory option is enabled if ( params.hasCreateOption( WinNT.CreateDirectory) == false) throw new IOException( "Create directory called for non-directory"); // Use the database interface to create the new file record int fid = dbCtx.getDBInterface().createFileRecord(dname, dirId, params, retain); // Indicate that the path exists fstate.setFileStatus( FileStatus.DirectoryExists); // Set the file id for the new directory fstate.setFileId(fid); // If retention is enabled get the expiry date/time if ( dbCtx.hasRetentionPeriod() && retain == true) { RetentionDetails retDetails = dbCtx.getDBInterface().getFileRetentionDetails(dirId, fid); if ( retDetails != null) fstate.setRetentionExpiryDateTime(retDetails.getEndTime()); } // Check if the file loader handles create directory requests if ( fid != -1 && dbCtx.getFileLoader() instanceof NamedFileLoader) { // Create the directory in the filesystem/repository NamedFileLoader namedLoader = (NamedFileLoader) dbCtx.getFileLoader(); namedLoader.createDirectory(params.getPath(), fid); } } catch (Exception ex) { Debug.println(ex); } } /** * Create a new file entry * * @param sess SrvSession * @param tree TreeConnection * @param params FileOpenParams * @return NetworkFile */ public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException { // Access the database context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Set the session in the file open parameters params.setSession( sess); // Get, or create, a file state for the new file FileState fstate = getFileState(params.getPath(), dbCtx, true); if ( fstate.fileExists()) { // Check if the file creation is a new stream if ( params.isStream() == false) throw new FileExistsException("File exists, " + params.getPath()); // Create a new stream associated with the existing file return createStream(params, fstate, dbCtx); } // Split the path string and find the directory id to attach the file to int dirId = findParentDirectoryId(dbCtx,params.getPath(),true); if ( dirId == -1) throw new IOException("Cannot find parent directory"); // Check if the allocation size for the new file is greater than the maximum allowed file size if ( dbCtx.hasMaximumFileSize() && params.getAllocationSize() > dbCtx.getMaximumFileSize()) throw new DiskFullException( "Required allocation greater than maximum file size"); // Create a new file DBNetworkFile file = null; try { // Get the file name String[] paths = FileName.splitPath(params.getPath()); String fname = paths[1]; // Check if the file name is too long if ( fname != null && fname.length() > MaxFileNameLen) throw new FileNameException("File name too long, " + fname); // If retention is enabled check if the file is a temporary file boolean retain = true; if ( dbCtx.hasRetentionPeriod()) { // Check if the file is marked delete on close if ( params.isDeleteOnClose()) retain = false; } // Set the default NFS file mode, if not set if ( params.hasMode() == false) params.setMode(DefaultNFSFileMode); // Create a new file record int fid = dbCtx.getDBInterface().createFileRecord(fname, dirId, params, retain); // Indicate that the file exists fstate.setFileStatus( FileStatus.FileExists); // Save the file id fstate.setFileId(fid); // If retention is enabled get the expiry date/time if ( dbCtx.hasRetentionPeriod() && retain == true) { RetentionDetails retDetails = dbCtx.getDBInterface().getFileRetentionDetails(dirId, fid); if ( retDetails != null) fstate.setRetentionExpiryDateTime(retDetails.getEndTime()); } // Create a network file to hold details of the new file entry file = (DBNetworkFile) dbCtx.getFileLoader().openFile(params, fid, 0, dirId, true, false); file.setFullName(params.getPath()); file.setDirectoryId(dirId); file.setAttributes(params.getAttributes()); file.setFileState(fstate); // Open the file file.openFile(true); } catch (DBException ex) { // Remove the file state for the new file dbCtx.getStateCache().removeFileState( fstate.getPath()); // DEBUG Debug.println("Create file error: " + ex.toString()); // Debug.println(ex); } // Return the new file details if (file == null) throw new IOException( "Failed to create file " + params.getPath()); else { // Update the file state fstate.incrementOpenCount(); } // Return the network file return file; } /** * Delete a directory * * @param sess Session details * @param tree Tree connection * @param dir Path of directory to delete * @exception IOException */ public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB deleteDirectory() dir=" + dir); // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Get the file state for the path FileState fstate = getFileState(dir,dbCtx,false); if ( fstate != null && fstate.fileExists() == false) throw new FileNotFoundException("Path does not exist, " + dir); // Create a file state if it does not exist if ( fstate == null) fstate = getFileState(dir,dbCtx,true); // Get the directory details DBFileInfo dinfo = getFileDetails(dir, dbCtx,fstate); if ( dinfo == null) throw new FileNotFoundException(dir); // Check if the directory contains any files try { // Check if the file loader handles delete directory requests. Called first as the loader may throw an exception // to stop the directory being deleted. if ( dbCtx.isTrashCanEnabled() == false && dbCtx.getFileLoader() instanceof NamedFileLoader) { // Delete the directory in the filesystem/repository NamedFileLoader namedLoader = (NamedFileLoader) dbCtx.getFileLoader(); namedLoader.deleteDirectory(dir, dinfo.getFileId()); } // Delete the directory file record, or mark as deleted if the trashcan is enabled dbCtx.getDBInterface().deleteFileRecord(dinfo.getDirectoryId(), dinfo.getFileId(), dbCtx.isTrashCanEnabled()); // Indicate that the path does not exist fstate.setFileStatus( FileStatus.NotExist); fstate.setFileId(-1); fstate.removeAttribute(FileState.FileInformation); } catch (DBException ex) { Debug.println( ex); throw new IOException(); } } /** * Delete a file * * @param sess Session details * @param tree Tree connection * @param name Name of file to delete * @exception IOException */ public void deleteFile(SrvSession sess, TreeConnection tree, String name) throws IOException { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Check if the file name is a stream if ( FileName.containsStreamName(name)) { // Delete a stream within a file deleteStream(sess, tree, name); return; } // Get the file state for the path FileState fstate = getFileState(name, dbCtx, false); if ( fstate != null && fstate.fileExists() == false) throw new FileNotFoundException("File does not exist, " + name); // Create a file state for the file, if not already valid if ( fstate == null) fstate = getFileState(name, dbCtx, true); try { // Check if the file is within an active retention period getRetentionDetailsForState( dbCtx, fstate); if ( fstate.hasActiveRetentionPeriod()) throw new AccessDeniedException("File retention active"); // Get the file details DBFileInfo dbInfo = getFileDetails(name, dbCtx, fstate); if ( dbInfo == null) throw new FileNotFoundException(name); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("DBDiskDriver deleteFile() name=" + name + ", state=" + fstate); // Delete the file in the filesystem/repository, the loader may prevent the file delete by throwing // an exception if ( dbCtx.isTrashCanEnabled() == false) dbCtx.getFileLoader().deleteFile(name, dbInfo.getFileId(), 0); // If the file is a symbolic link delete the symbolic link record if ( dbInfo.isFileType() == FileType.SymbolicLink) dbCtx.getDBInterface().deleteSymbolicLinkRecord( dbInfo.getDirectoryId(), dbInfo.getFileId()); // Delete the file record dbCtx.getDBInterface().deleteFileRecord(dbInfo.getDirectoryId(), dbInfo.getFileId(), dbCtx.isTrashCanEnabled()); // Indicate that the path does not exist fstate.setFileStatus( FileStatus.NotExist); fstate.setFileId(-1); fstate.removeAttribute(FileState.FileInformation); // Check if there is a quota manager, if so then release the file space if ( dbCtx.hasQuotaManager()) { // Release the file space back to the filesystem free space dbCtx.getQuotaManager().releaseSpace(sess, tree, dbInfo.getFileId(), null, dbInfo.getSize()); } } catch (DBException ex) { Debug.println(ex); throw new IOException(); } } /** * Check if the specified file exists, and it is a file. * * @param sess Session details * @param tree Tree connection * @param name File name * @return int */ public int fileExists(SrvSession sess, TreeConnection tree, String name) { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the path contains an NTFS stream name int fileSts = FileStatus.NotExist; if ( FileName.containsStreamName(name)) { // Split the path into directory, file and stream name components String[] paths = FileName.splitPathStream(name); // Get, or create, the file state for main file path String filePath = paths[0] + paths[1]; FileState fstate = getFileState(filePath,dbCtx,true); // Check if the top level file exists if ( fstate != null && fstate.fileExists() == true) { // Get the top level file details DBFileInfo dbInfo = getFileDetails(name, dbCtx, fstate); if ( dbInfo != null) { // Checkif the streams list is cached StreamInfoList streams = (StreamInfoList) fstate.findAttribute(DBStreamList); // Get the list of available streams if ( streams == null) { // Load the streams list for the file streams = loadStreamList(fstate, dbInfo, dbCtx, true); // Cache the streams list if ( streams != null) fstate.addAttribute(DBStreamList, streams); } if ( streams != null && streams.numberOfStreams() > 0) { // Check if the required stream exists if ( streams.findStream(paths[2]) != null) fileSts = FileStatus.FileExists; } } } // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB fileExists() name=" + filePath + ", stream=" + paths[2] + ", fileSts=" + FileStatus.asString(fileSts)); } else { // Get, or create, the file state for the path FileState fstate = getFileState( name, dbCtx, true); // Check if the file exists status has been cached fileSts = fstate.getFileStatus(); if ( fstate.getFileStatus() == FileStatus.Unknown) { // Get the file details DBFileInfo dbInfo = getFileDetails(name,dbCtx,fstate); if ( dbInfo != null) { if ( dbInfo.isDirectory() == true) fileSts = FileStatus.DirectoryExists; else fileSts = FileStatus.FileExists; } else { // Indicate that the file does not exist fstate.setFileStatus( FileStatus.NotExist); fileSts = FileStatus.NotExist; } // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB fileExists() name=" + name + ", fileSts=" + FileStatus.asString(fileSts)); } else { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("@@ Cache hit - fileExists() name=" + name + ", fileSts=" + FileStatus.asString(fileSts)); } } // Return the file exists status return fileSts; } /** * Flush buffered data for the specified file * * @param sess Session details * @param tree Tree connection * @param file Network file * @exception IOException */ public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB flushFile()"); // Flush any buffered data file.flushFile(); } /** * Return file information about the specified file * * @param sess Session details * @param tree Tree connection * @param name File name * @return SMBFileInfo * @exception IOException */ public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, String name) throws IOException { // Check for the null file name if (name == null) return null; // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Check if the path is a file stream FileState fstate = null; FileInfo finfo = null; if ( FileName.containsStreamName(name)) { // Check if there is an active file state for the stream fstate = getFileState(name,dbCtx,true); if ( fstate != null) { // Check if the file information is available finfo = (FileInfo) fstate.findAttribute(FileState.FileInformation); } // If the cached file information is not available then create it if ( finfo == null) { // Split the path into directory, file and stream name components String[] paths = FileName.splitPathStream(name); // Get, or create, the file state for main file path String filePath = paths[0] + paths[1]; FileState parent = getFileState(filePath,dbCtx,true); // Check if the top level file exists if ( parent != null && parent.fileExists() == true) { // Get the top level file details DBFileInfo dbInfo = getFileDetails(name,dbCtx,parent); if ( dbInfo != null) { // Get the list of available streams StreamInfoList streams = loadStreamList(parent, dbInfo, dbCtx, true); if ( streams != null && streams.numberOfStreams() > 0) { // Get the details for the stream, if the information is valid copy it to a file information // object StreamInfo sInfo = streams.findStream(paths[2]); if ( sInfo != null) { // Create a file information object, copy the stream details to it finfo = new DBFileInfo(paths[1], name, dbInfo.getFileId(), dbInfo.getDirectoryId()); finfo.setFileId(sInfo.getFileId()); finfo.setFileSize(sInfo.getSize()); // Use the parent files timestamps for now finfo.setCreationDateTime(dbInfo.getCreationDateTime()); finfo.setAccessDateTime(dbInfo.getAccessDateTime()); finfo.setModifyDateTime(dbInfo.getModifyDateTime()); // Attach to the file state fstate.addAttribute(FileState.FileInformation, finfo); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("getFileInformation() stream=" + name + ", info=" + finfo); } } } } } } else { // Get, or create, the file state for the path fstate = getFileState(name, dbCtx, true); // Get the file details for the path DBFileInfo dbInfo = getFileDetails(name, dbCtx, fstate); // Set the full file/path name if ( dbInfo != null) dbInfo.setFullName(name); finfo = dbInfo; } // DEBUG if ( Debug.EnableInfo && hasDebug() && finfo != null) Debug.println("getFileInformation info=" + finfo.toString()); // Return the file information return finfo; } /** * Determine if the disk device is read-only. * * @param sess Session details * @param ctx Device context * @return true if the device is read-only, else false * @exception IOException If an error occurs. */ public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException { return false; } /** * Open a file * * @param sess Session details * @param tree Tree connection * @param params File open parameters * @return NetworkFile * @exception IOException */ public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Set the session if the file open parameters params.setSession( sess); // Get, or create, the file state FileState fstate = getFileState(params.getPath(), dbCtx, true); // Check if we are opening a stream associated with the main file if ( fstate != null && params.isStream()) { // Open an NTFS stream return openStream(params, fstate, dbCtx); } // Get the file name String[] paths = FileName.splitPath(params.getPath()); String fname = paths[1]; // Check if the file name is too long if ( fname != null && fname.length() > MaxFileNameLen) throw new FileNameException("File name too long, " + fname); // Get the file information DBFileInfo finfo = getFileDetails(params.getPath(), dbCtx, fstate); if (finfo == null) throw new AccessDeniedException(); // If retention is enabled get the expiry date/time if ( dbCtx.hasRetentionPeriod()) { try { // Get the file retention expiry date/time RetentionDetails retDetails = dbCtx.getDBInterface().getFileRetentionDetails(finfo.getDirectoryId(), finfo.getFileId()); if ( retDetails != null) fstate.setRetentionExpiryDateTime( retDetails.getEndTime()); } catch (DBException ex) { throw new AccessDeniedException("Retention error, " + ex.getMessage()); } } // Check if the file shared access indicates exclusive file access if ( params.getSharedAccess() == SharingMode.NOSHARING && fstate.getOpenCount() > 0) throw new FileSharingException("File already open, " + params.getPath()); // Check if the file is read-only and write access has been requested if (( params.isReadWriteAccess() || params.isWriteOnlyAccess())) { // Check if the file is read-only if ( finfo.isReadOnly()) throw new AccessDeniedException("Read-only file"); else if ( fstate.hasActiveRetentionPeriod()) throw new AccessDeniedException("File retention active"); } // Create a JDBC network file and open the top level file if ( Debug.EnableInfo && hasDebug()) Debug.println("DB openFile() name=" + params.getPath()); DBNetworkFile jdbcFile = (DBNetworkFile) dbCtx.getFileLoader().openFile(params, finfo.getFileId(), 0, finfo.getDirectoryId(), false, finfo.isDirectory()); jdbcFile.setFileDetails(finfo); jdbcFile.setFileState(fstate); // Set the file owner if ( sess != null) jdbcFile.setOwnerSessionId(sess.getUniqueId()); // Update the file open count fstate.setFileStatus( finfo.isDirectory() ? FileStatus.DirectoryExists : FileStatus.FileExists); fstate.incrementOpenCount(); // Return the network file return jdbcFile; } /** * Read a block of data from a file * * @param sess Session details * @param tree Tree connection * @param file Network file * @param buf Buffer to return data to * @param bufPos Starting position in the return buffer * @param siz Maximum size of data to return * @param pos File offset to read data * @return Number of bytes read * @exception IOException */ public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buf, int bufPos, int siz, long pos) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB readFile() filePos=" + pos + ", len=" + siz); // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Check that the network file is our type int rxsiz = 0; if (file instanceof DBNetworkFile) { // Access the JDBC network file DBNetworkFile jfile = (DBNetworkFile) file; // Check if there are any locks on the file // Check if there are any locks on the file if ( jfile.hasFileState() && jfile.getFileState().hasActiveLocks()) { // Check if this session has write access to the required section of the file if ( jfile.getFileState().canReadFile( pos, siz, sess.getProcessId()) == false) throw new LockConflictException(); } // Debug if ( jfile.getFileState().getOpenCount() == 0) Debug.println("**** readFile() Open Count Is ZERO ****"); // Read from the file rxsiz = jfile.readFile(buf, siz, bufPos, pos); // Check if we have reached the end of file if ( rxsiz == -1) rxsiz = 0; } // Return the actual read length return rxsiz; } /** * Rename a file * * @param sess Session details * @param tree Tree connection * @param oldName Existing file name * @param newName New file name * @exception IOException */ public void renameFile(SrvSession sess, TreeConnection tree, String oldName, String newName) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB renameFile() from=" + oldName + " to=" + newName); // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Get, or create, the file state for the existing file FileState fstate = getFileState(oldName, dbCtx, true); try { // Get the file name String[] paths = FileName.splitPath( newName); String fname = paths[1]; // Check if the file name is too long if ( fname != null && fname.length() > MaxFileNameLen) throw new FileNameException("Destination name too long, " + fname); // Check if the file is within an active retention period getRetentionDetailsForState( dbCtx, fstate); if ( fstate.hasActiveRetentionPeriod()) throw new AccessDeniedException("File retention active"); // Get the file id of the existing file int fid = fstate.getFileId(); int dirId = -1; if ( fid == -1) { // Split the current path string and find the file id of the existing file/directory dirId = findParentDirectoryId(dbCtx, oldName, true); if ( dirId == -1) throw new FileNotFoundException(oldName); // Get the current file/directory name String[] oldPaths = FileName.splitPath(oldName); fname = oldPaths[1]; // Get the file id fid = getFileId(oldName, fname, dirId, dbCtx); if ( fid == -1) throw new FileNotFoundException(oldName); // Update the file state fstate.setFileId(fid); } // Get the existing file/directory details DBFileInfo curInfo = getFileDetails(oldName, dbCtx, fstate); if ( dirId == -1 && curInfo != null) dirId = curInfo.getDirectoryId(); // Check if the new name file/folder already exists DBFileInfo newInfo = getFileDetails(newName, dbCtx); if ( newInfo != null) throw new FileExistsException("Rename to file/folder already exists," + newName); // Check if the loader handles rename requests, an exception may be thrown by the loader // to prevent the file/directory rename. if ( dbCtx.getFileLoader() instanceof NamedFileLoader) { // Rename the file/directory NamedFileLoader namedLoader = (NamedFileLoader) dbCtx.getFileLoader(); namedLoader.renameFileDirectory(oldName, fid, newName, curInfo.isDirectory()); } // Get the new file/directory name int newDirId = findParentDirectoryId(dbCtx, newName, true); if ( newDirId == -1) throw new FileNotFoundException(newName); String[] newPaths = FileName.splitPath(newName); String newFname = newPaths[1]; // Rename the file/folder, this may also link the file/folder to a new parent directory dbCtx.getDBInterface().renameFileRecord(dirId, fid, newFname, newDirId); // Update the file state with the new file name/path dbCtx.getStateCache().renameFileState(newName, fstate, curInfo.isDirectory()); // Remove any cached file information fstate.removeAttribute(FileState.FileInformation); } catch (DBException ex) { throw new FileNotFoundException(oldName); } } /** * Seek to the specified point within a file * * @param sess Session details * @param tree Tree connection * @param file Network file * @param pos New file position * @param typ Seek type * @return New file position * @exception IOException */ public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB seekFile()"); // Check that the network file is our type long newpos = 0; if (file instanceof DBNetworkFile) { // Seek within the file DBNetworkFile jfile = (DBNetworkFile) file; newpos = jfile.seekFile(pos, typ); } // Return the new file position return newpos; } /** * Set file information * * @param sess Session details * @param tree Tree connection * @param name File name * @param info File information to be set * @exception IOException */ public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB setFileInformation() name=" + name + ", info=" + info.toString()); // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) { // Check if the delete on close flag is being set if ( info.getSetFileInformationFlags() == FileInfo.SetDeleteOnClose) { // Just return, file object will be marked for delete on close return; } else throw new DiskOfflineException( "Database is offline"); } // Get, or create, the file state FileState fstate = getFileState(name, dbCtx, true); // Get the file details DBFileInfo dbInfo = getFileDetails( name, dbCtx, fstate); if ( dbInfo == null) throw new FileNotFoundException(name); try { // Check if the file is within an active retention period getRetentionDetailsForState( dbCtx, fstate); if ( fstate.hasActiveRetentionPeriod()) throw new AccessDeniedException("File retention active"); // Check if the loader handles set file information requests, an exception may be thrown by the loader // to prevent the update if ( dbCtx.getFileLoader() instanceof NamedFileLoader) { // Set the file information NamedFileLoader namedLoader = (NamedFileLoader) dbCtx.getFileLoader(); namedLoader.setFileInformation(name, dbInfo.getFileId(), info); } // Check if the inode change date/time has been set if ( info.hasChangeDateTime() == false) { info.setChangeDateTime(System.currentTimeMillis()); if ( info.hasSetFlag(FileInfo.SetChangeDate) == false) info.setFileInformationFlags(info.getSetFileInformationFlags() + FileInfo.SetChangeDate); } // Update the file information dbCtx.getDBInterface().setFileInformation(dbInfo.getDirectoryId(), dbInfo.getFileId(), info); // Copy the updated values to the file state if ( info.hasSetFlag(FileInfo.SetFileSize)) dbInfo.setFileSize(info.getSize()); if ( info.hasSetFlag(FileInfo.SetAllocationSize)) dbInfo.setAllocationSize(info.getAllocationSize()); if ( info.hasSetFlag(FileInfo.SetAccessDate)) dbInfo.setAccessDateTime(info.getAccessDateTime()); if ( info.hasSetFlag(FileInfo.SetCreationDate)) dbInfo.setAccessDateTime(info.getCreationDateTime()); if ( info.hasSetFlag(FileInfo.SetModifyDate)) dbInfo.setAccessDateTime(info.getModifyDateTime()); if ( info.hasSetFlag(FileInfo.SetChangeDate)) dbInfo.setAccessDateTime(info.getChangeDateTime()); if ( info.hasSetFlag(FileInfo.SetGid)) dbInfo.setGid(info.getGid()); if ( info.hasSetFlag(FileInfo.SetUid)) dbInfo.setUid(info.getUid()); if ( info.hasSetFlag(FileInfo.SetMode)) dbInfo.setMode(info.getMode()); if ( info.hasSetFlag(FileInfo.SetAttributes)) dbInfo.setFileAttributes(info.getFileAttributes()); // Update the file state fstate.setFileId(dbInfo.getFileId()); } catch (DBException ex) { throw new IOException(); } } /** * Start a search of the file system * * @param sess SrvSession * @param tree TreeConnection * @param searchPath String * @param attrib int * @return SearchContext * @exception FileNotFoundException */ public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attrib) throws FileNotFoundException { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new FileNotFoundException( "Database is offline"); // Prepend a leading slash to the path if not on the search path if ( searchPath.startsWith("\\") == false) searchPath = "\\" + searchPath; // Get the directory id for the last directory in the path int dirId = findParentDirectoryId(dbCtx,searchPath,true); if ( dirId == -1) throw new FileNotFoundException("Invalid path"); // Start the search SearchContext search = null; try { // Check if the search path is a none wildcard search, the file information may be in the // state cache if ( WildCard.containsWildcards( searchPath) == false) { // Check if there is a file state for the search path FileState searchState = getFileState( searchPath, dbCtx, false); if ( searchState != null && searchState.fileExists() == true) { // Check if the file state has the file information attached DBFileInfo finfo = (DBFileInfo) searchState.findAttribute(FileState.FileInformation); if ( finfo != null) { // Create a single file search context using the cached file information search = new CachedSearchContext( finfo); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("DB StartSearch using cached file information, path=" + searchPath + ", info=" + finfo); } } } // Start the search via the database interface, if the search is not valid if ( search == null) { // Start the search DBSearchContext dbSearch = dbCtx.getDBInterface().startSearch(dirId, searchPath, attrib, DBInterface.FileAll, -1); // Check if files should be marked as offline dbSearch.setMarkAsOffline( dbCtx.hasOfflineFiles()); dbSearch.setOfflineFileSize( dbCtx.getOfflineFileSize()); search = dbSearch; } } catch ( DBException ex) { throw new FileNotFoundException(); } // Return the search context return search; } /** * Truncate a file to the specified size * * @param sess Server session * @param tree Tree connection * @param file Network file details * @param siz New file length * @exception java.io.IOException The exception description. */ public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long siz) throws java.io.IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB truncateFile()"); // Check that the network file is our type if (file instanceof DBNetworkFile) { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Get the JDBC file DBNetworkFile jfile = (DBNetworkFile) file; // Get, or create, the file state FileState fstate = jfile.getFileState(); // Get the file details DBFileInfo dbInfo = getFileDetails(jfile.getFullName(),dbCtx,fstate); if ( dbInfo == null) throw new FileNotFoundException(jfile.getFullName()); // Check if the new file size is greater than the maximum allowed file size, if enabled if ( dbCtx.hasMaximumFileSize() && siz > dbCtx.getMaximumFileSize()) { // Mark the file to delete on close file.setDeleteOnClose( true); // Return a disk full error throw new DiskFullException( "Write is beyond maximum allowed file size"); } // Keep track of the allocation/release size in case the file resize fails long allocSize = 0L; long releaseSize = 0L; // Check if there is a quota manager QuotaManager quotaMgr = dbCtx.getQuotaManager(); if ( dbCtx.hasQuotaManager()) { // Determine if the new file size will release space or require space allocating if ( siz > dbInfo.getAllocationSize()) { // Calculate the space to be allocated allocSize = siz - dbInfo.getAllocationSize(); // Allocate space to extend the file quotaMgr.allocateSpace(sess, tree, file, allocSize); } else { // Calculate the space to be released as the file is to be truncated, release the space if // the file truncation is successful releaseSize = dbInfo.getAllocationSize() - siz; } } // Set the file length try { jfile.truncateFile(siz); } catch (IOException ex) { // Check if we allocated space to the file if ( allocSize > 0 && quotaMgr != null) quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, allocSize); // Rethrow the exception throw ex; } // Check if space has been released by the file resizing if ( releaseSize > 0 && quotaMgr != null) quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, releaseSize); // Update the file information if ( allocSize > 0) dbInfo.setAllocationSize(dbInfo.getAllocationSize() + allocSize); else if ( releaseSize > 0) dbInfo.setAllocationSize(dbInfo.getAllocationSize() - releaseSize); // Update the last file change date/time try { // Build the file information to set the change date/time FileInfo finfo = new FileInfo(); finfo.setChangeDateTime(System.currentTimeMillis()); finfo.setFileInformationFlags(FileInfo.SetChangeDate); // Set the file change date/time dbCtx.getDBInterface().setFileInformation(jfile.getDirectoryId(), jfile.getFileId(), finfo); // Update the cached file information dbInfo.setChangeDateTime(finfo.getChangeDateTime()); dbInfo.setAllocationSize(siz); } catch (Exception ex) { } } } /** * Write a block of data to a file * * @param sess Session details * @param tree Tree connection * @param file Network file * @param buf Data to be written * @param bufoff Offset of data within the buffer * @param siz Number of bytes to be written * @param fileoff Offset within the file to start writing the data */ public int writeFile(SrvSession sess,TreeConnection tree,NetworkFile file,byte[] buf,int bufoff,int siz,long fileoff) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB writeFile()"); // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Check if the database is online if ( dbCtx.getDBInterface().isOnline() == false) throw new DiskOfflineException( "Database is offline"); // Check that the network file is our type if (file instanceof DBNetworkFile) { // Access the JDBC network file DBNetworkFile jfile = (DBNetworkFile) file; // Check if there are any locks on the file if ( jfile.hasFileState() && jfile.getFileState().hasActiveLocks()) { // Check if this session has write access to the required section of the file if ( jfile.getFileState().canWriteFile( fileoff, siz, sess.getProcessId()) == false) throw new LockConflictException(); } // Check if there is a maximum file size, if so then check if the write is beyond the allowed file size if ( dbCtx.hasMaximumFileSize() && (fileoff + siz) > dbCtx.getMaximumFileSize()) { // Mark the file to delete on close file.setDeleteOnClose( true); // Return a disk full error throw new DiskFullException( "Write is beyond maximum allowed file size"); } // Check if there is a quota manager QuotaManager quotaMgr = dbCtx.getQuotaManager(); if ( quotaMgr != null) { // Get the file information DBFileInfo finfo = getFileDetails(jfile.getFullName(), dbCtx, jfile.getFileState()); if ( finfo == null) throw new FileNotFoundException(jfile.getFullName()); // Check if the file requires extending long extendSize = 0L; long endOfWrite = fileoff + siz; if ( endOfWrite > finfo.getAllocationSize()) { // Calculate the amount the file must be extended extendSize = endOfWrite - finfo.getAllocationSize(); // Allocate space for the file extend quotaMgr.allocateSpace(sess, tree, file, extendSize); } // Write to the file try { jfile.writeFile(buf, siz, bufoff, fileoff); } catch (IOException ex) { // Check if we allocated space to the file if ( extendSize > 0 && quotaMgr != null) quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, extendSize); // Rethrow the exception throw ex; } // Update the file information if ( extendSize > 0) finfo.setAllocationSize(endOfWrite); } else { // Write to the file jfile.writeFile(buf, siz, bufoff, fileoff); } } // Return the actual write length return siz; } /** * Parse/validate the parameter string and create a device context for this share * * @param shareName String * @param args ConfigElement * @return DeviceContext * @exception DeviceContextException */ public DeviceContext createContext(String shareName, ConfigElement args) throws DeviceContextException { // Check the arguments if (args.getChildCount() < 3) throw new DeviceContextException("Not enough context arguments"); // Check for the debug enable flags if ( args.getChild("Debug") != null) m_debug = true; // Create the database device context DBDeviceContext ctx = new DBDeviceContext(args); // Return the database device context return ctx; } /** * Get the file id for a file * * @param path String * @param dbCtx DBDeviceContext * @return DBFileInfo */ protected final DBFileInfo getFileDetails(String path, DBDeviceContext dbCtx) { return getFileDetails(path, dbCtx, null); } /** * Get the file id for a file * * @param path String * @param dbCtx DBDeviceContext * @param fstate FileState * @return DBFileInfo */ protected final DBFileInfo getFileDetails(String path, DBDeviceContext dbCtx, FileState fstate) { // Check if the file details are attached to the file state if ( fstate != null) { // Return the file information DBFileInfo finfo = (DBFileInfo) fstate.findAttribute(FileState.FileInformation); if ( finfo != null) return finfo; } // Check for the root directory if ( path.length() == 0 || path.compareTo("\\") == 0) { // Get the root directory information from the device context DBFileInfo rootDir = dbCtx.getRootDirectoryInfo(); // Mark the directory as existing if ( fstate != null) fstate.setFileStatus( FileStatus.DirectoryExists); return rootDir; } // Split the path string and find the parent directory id int dirId = findParentDirectoryId(dbCtx,path,true); if ( dirId == -1) return null; // Get the file name String[] paths = FileName.splitPathStream(path); String fname = paths[1]; String filePath = null; if ( paths[0] != null && paths[0].endsWith(FileName.DOS_SEPERATOR_STR) == false) filePath = paths[0] + FileName.DOS_SEPERATOR_STR + paths[1]; else filePath = paths[0] + paths[1]; // Get the file id for the specified file int fid = getFileId(filePath, fname, dirId, dbCtx); if (fid == -1) return null; // Create the file information DBFileInfo finfo = getFileInfo( filePath, dirId, fid, dbCtx); if ( finfo != null && fstate != null) { // Set various file state details fstate.setFileStatus( finfo.isDirectory() ? FileStatus.DirectoryExists : FileStatus.FileExists); fstate.setFileId(finfo.getFileId()); // Set the file name finfo.setFileName( fname); finfo.setFullName(path); // check if files should be marked as offline if ( dbCtx.hasOfflineFiles() && finfo.hasAttribute( FileAttribute.NTOffline) == false) { if ( dbCtx.getOfflineFileSize() == 0 || finfo.getSize() >= dbCtx.getOfflineFileSize()) finfo.setFileAttributes( finfo.getFileAttributes() + FileAttribute.NTOffline); } } // Check if the path is a file stream if ( paths[2] != null) { // Get the file information for the stream finfo = getStreamInfo(fstate, paths, dbCtx); } // Return the file/stream information return finfo; } /** * Get the file id for a file * * @param path String * @param name String * @param dirId int * @param dbCtx DBDeviceContext * @return int */ protected final int getFileId(String path, String name, int dirId, DBDeviceContext dbCtx) { // Check if the file is in the cache FileStateCache cache = dbCtx.getStateCache(); FileState state = null; if ( cache != null) { // Search for the file state state = cache.findFileState(path); if ( state != null) { // Checkif the file id is cached if ( state.getFileId() != -1) { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("@@ Cache hit - getFileId() name=" + name); // Return the file id return state.getFileId(); } else if ( state.getFileStatus() == FileStatus.NotExist) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("@@ Cache hit - getFileStatus() name=" + name + ", sts=NotExist"); // Indicate that the file does not exist return -1; } } } // Get the file id from the database int fileId = -1; try { // Get the file id fileId = dbCtx.getDBInterface().getFileId(dirId, name, false, false); } catch (DBException ex) { } // Update the cache entry, if available if ( state != null) state.setFileId(fileId); // Return the file id, or -1 if the file was not found return fileId; } /** * Load the retention details for a file state, if enabled * * @param dbCtx DBDeviceContext * @param fstate FileState * @exception DBException */ protected final void getRetentionDetailsForState(DBDeviceContext dbCtx, FileState fstate) throws DBException { // If retention is enabled get the expiry date/time if ( dbCtx.hasRetentionPeriod()) { // Get the file retention expiry date/time RetentionDetails retDetails = dbCtx.getDBInterface().getFileRetentionDetails(-1, fstate.getFileId()); if ( retDetails != null) fstate.setRetentionExpiryDateTime( retDetails.getEndTime()); } } /** * Find the directory id for the parent directory in the specified path * * @param ctx DBDeviceContext * @param path String * @param filePath boolean * @return int */ protected final int findParentDirectoryId(DBDeviceContext ctx, String path, boolean filePath) { // Split the path String[] paths = null; if ( path != null && path.startsWith("\\")) { // Split the path paths = FileName.splitPath(path); } else { // Add a leading slash to the path before parsing paths = FileName.splitPath("\\" + path); } if ( paths[0] != null && paths[0].compareTo("\\") == 0 || paths[0].startsWith("\\") == false) return 0; // Check if the file is in the cache FileStateCache cache = ctx.getStateCache(); FileState state = null; if ( cache != null) { // Search for the file state state = cache.findFileState(paths[0]); if ( state != null && state.getFileId() != -1) { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("@@ Cache hit - findParentDirectoryId() path=" + paths[0]); // Return the file id return state.getFileId(); } } // Get the directory id list int[] ids = findParentDirectoryIdList(ctx,path,filePath); if ( ids == null) return -1; // Check for the root directory id only if ( ids.length == 1) return ids[0]; // Return the directory id of the last directory int idx = ids.length - 1; if ( filePath == true && ids[ids.length - 1] == -1) idx--; return ids[idx]; } /** * Find the directory ids for the specified path list * * @param ctx DBDeviceContext * @param path String * @param filePath boolean * @return int[] */ protected final int[] findParentDirectoryIdList(DBDeviceContext ctx, String path, boolean filePath) { // Validate the paths and check for the root path String[] paths = FileName.splitAllPaths(path); if ( paths == null || paths.length == 0) return null; if ( paths[0].compareTo("*.*") == 0 || paths[0].compareTo("*") == 0 || (filePath == true && paths.length == 1)) { int[] ids = { 0 }; return ids; } if ( paths[0].startsWith("\\")) { // Trim the leading slash from the first path paths[0] = paths[0].substring(1); } // Find the directory id by traversing the list of directories int endIdx = paths.length - 1; if ( filePath == true) endIdx--; // If there are no paths to check then return the root directory id if ( endIdx <= 1 && paths[0].length() == 0) { int[] ids = new int[1]; ids[0] = 0; return ids; } // Allocate the directory id list int[] ids = new int[paths.length]; for ( int i = 0; i < ids.length; i++) ids[i] = -1; // Build up the current path as we traverse the list StringBuffer pathStr = new StringBuffer("\\"); // Check for paths in the file state cache FileStateCache cache = ctx.getStateCache(); FileState fstate = null; // Traverse the path list, initialize the directory id to the root id int dirId = 0; int parentId = -1; int idx = 0; try { // Loop until the end of the path list while ( idx <= endIdx) { // Get the current path, and add to the full path string String curPath = paths[idx]; pathStr.append(curPath); // Check if there is a file state for the current path fstate = cache.findFileState(pathStr.toString()); if ( fstate != null && fstate.getFileId() != -1) { // Get the file id from the cached information ids[idx] = fstate.getFileId(); parentId = dirId; dirId = ids[idx]; } else { // Search for the current directory in the database parentId = dirId; dirId = ctx.getDBInterface().getFileId(dirId, curPath, true, true); if ( dirId != -1) { // Get the next directory id ids[idx] = dirId; // Check if we have a file state, or create a new file state for the current path if ( fstate != null) { // Set the file id for the file state fstate.setFileId(dirId); } else { // Create a new file state for the current path fstate = cache.findFileState(pathStr.toString(), true); // Get the file information DBFileInfo finfo = ctx.getDBInterface().getFileInformation(parentId, dirId, DBInterface.FileAll); fstate.addAttribute(FileState.FileInformation, finfo); fstate.setFileStatus( finfo.isDirectory() ? FileStatus.DirectoryExists : FileStatus.FileExists); fstate.setFileId(dirId); } } else return null; } // Update the path index idx++; // Update the current path string pathStr.append("\\"); } } catch (DBException ex) { Debug.println(ex); return null; } // Return the directory id list return ids; } /** * Return file information about the specified file, using the internal file id * * @param path String * @param dirId int * @param fid int * @param dbCtx DBDeviceContext * @return DBFileInfo * @exception IOException */ public DBFileInfo getFileInfo(String path, int dirId, int fid, DBDeviceContext dbCtx) { // Check if the file is in the cache FileState state = getFileState(path, dbCtx, true); if ( state != null && state.getFileId() != -1) { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("@@ Cache hit - getFileInfo() path=" + path); // Return the file information DBFileInfo finfo = (DBFileInfo) state.findAttribute(FileState.FileInformation); if ( finfo != null) return finfo; } // Get the file information from the database DBFileInfo finfo = null; try { // Get the file information finfo = dbCtx.getDBInterface().getFileInformation(dirId, fid, DBInterface.FileAll); } catch (DBException ex) { Debug.println(ex); finfo = null; } // Set the full path for the file if ( finfo != null) finfo.setFullName(path); // Update the cached information, if available if ( state != null && finfo != null) { state.addAttribute(FileState.FileInformation, finfo); state.setFileStatus( finfo.isDirectory() ? FileStatus.DirectoryExists : FileStatus.FileExists); } // Return the file information return finfo; } /** * Get the details for a file stream * * @param parent FileState * @param paths String[] * @param dbCtx DBDeviceContext * @return DBFileInfo */ public DBFileInfo getStreamInfo(FileState parent, String[] paths, DBDeviceContext dbCtx) { // Check if the file is in the cache String streamPath = paths[0] + paths[1] + paths[2]; FileState state = getFileState(streamPath, dbCtx, true); if ( state != null && state.getFileId() != -1) { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("@@ Cache hit - getStreamInfo() path=" + streamPath); // Return the file information DBFileInfo finfo = (DBFileInfo) state.findAttribute(FileState.FileInformation); if ( finfo != null) return finfo; } // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("DBDiskDriver getStreamInfo parent=" + parent.getPath() + ", stream=" + paths[2]); // Get a list of the streams for the parent file DBFileInfo finfo = null; try { // Get the list of streams StreamInfoList sList = (StreamInfoList) parent.findAttribute(DBStreamList); if ( sList == null) { // No cached stream information, get the list from the database sList = dbCtx.getDBInterface().getStreamsList(parent.getFileId(), DBInterface.StreamAll); // Cache the information parent.addAttribute(DBStreamList, sList); } // Find the required stream information if ( sList != null) { // Find the required stream information StreamInfo sInfo = sList.findStream(paths[2]); // Convert the stream information to file information if ( sInfo != null) { // Load the stream information finfo = new DBFileInfo(); finfo.setFileId(parent.getFileId()); // Copy the stream information finfo.setFileName(sInfo.getName()); finfo.setSize(sInfo.getSize()); // Get the file creation date, or use the current date/time if ( sInfo.hasCreationDateTime()) finfo.setCreationDateTime(sInfo.getCreationDateTime()); // Get the modification date, or use the current date/time if ( sInfo.hasModifyDateTime()) finfo.setModifyDateTime(sInfo.getModifyDateTime()); else if ( sInfo.hasCreationDateTime()) finfo.setModifyDateTime(sInfo.getCreationDateTime()); // Get the last access date, or use the current date/time if ( sInfo.hasAccessDateTime()) finfo.setAccessDateTime(sInfo.getAccessDateTime()); else if ( sInfo.hasCreationDateTime()) finfo.setAccessDateTime(sInfo.getCreationDateTime()); } } } catch ( DBException ex) { Debug.println(ex); finfo = null; } // Set the full path for the file if ( finfo != null) finfo.setFullName(streamPath); // Update the cached information, if available if ( state != null && finfo != null) { state.addAttribute(FileState.FileInformation, finfo); state.setFileStatus( FileStatus.FileExists); } // Return the file information for the stream return finfo; } /** * Get the cached file state for the specified path * * @param path String * @param ctx DBDeviceContext * @param create boolean * @return FileState */ protected final FileState getFileState(String path, DBDeviceContext ctx, boolean create) { // Access the file state cache FileStateCache cache = ctx.getStateCache(); if ( cache == null) return null; // Return the required file state return cache.findFileState(path, create); } /** * Connection opened to this disk device * * @param sess Server session * @param tree Tree connection */ public void treeOpened(SrvSession sess, TreeConnection tree) { } /** * Connection closed to this device * * @param sess Server session * @param tree Tree connection */ public void treeClosed(SrvSession sess, TreeConnection tree) { } /** * Check if general debug output is enabled * * @return boolean */ protected final boolean hasDebug() { return m_debug; } /** * Return disk information about a shared filesystem * * @param ctx DiskDeviceContext * @param info SrvDiskInfo * @exception IOException */ public final void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo info) throws IOException { // Check if there is static disk size information available if ( ctx.hasDiskInformation()) info.copyFrom(ctx.getDiskInformation()); // Check that the context is a JDBC context if ( ctx instanceof DBDeviceContext) { // Access the associated file loader class, if it implements the disk size interface then call the loader // to fill in the disk size details DBDeviceContext dbCtx = (DBDeviceContext) ctx; if ( dbCtx.getFileLoader() instanceof DiskSizeInterface) { // Pass the disk size request to the associated file loader DiskSizeInterface sizeInterface = (DiskSizeInterface) dbCtx.getFileLoader(); sizeInterface.getDiskInformation(ctx, info); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("DBDiskDriver getDiskInformation() handed to file loader"); } } // Check if there is a quota manager, if so then get the current free space from the quota manager if ( ctx.hasQuotaManager()) { // Get the free space, in bytes, from the quota manager long freeSpace = ctx.getQuotaManager().getAvailableFreeSpace(); // Convert the free space to free units long freeUnits = freeSpace / info.getUnitSize(); info.setFreeUnits(freeUnits); } } /** * Determine if NTFS streams support is enabled. Check if the associated file loader * supports NTFS streams. * * @param sess SrvSession * @param tree TreeConnection * @return boolean */ public boolean hasStreamsEnabled(SrvSession sess, TreeConnection tree) { // Check that the context is a JDBC context if ( tree.getContext() instanceof DBDeviceContext) { // Access the associated file loader class to check if it supports NTFS streams DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); if ( dbCtx.hasNTFSStreamsEnabled()) { // Check if the file loader supports NTFS streams return dbCtx.getFileLoader().supportsStreams(); } } // Disable streams return false; } /** * Get the stream information for the specified file stream * * @param sess SrvSession * @param tree TreeConnection * @param streamInfo StreamInfo * @return StreamInfo * @exception IOException */ public StreamInfo getStreamInformation(SrvSession sess, TreeConnection tree, StreamInfo streamInfo) throws IOException { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("### getStreamInformation() called ###"); // TODO Auto-generated method stub return null; } /** * Return the list of available streams for the specified file * * @param sess SrvSession * @param tree TreeConnection * @param fileName String * @return StreamInfoList * @exception IOException */ public StreamInfoList getStreamList(SrvSession sess, TreeConnection tree, String fileName) throws IOException { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Get the file state for the top level file FileState fstate = getFileState(fileName, dbCtx, true); // Check if the file state already has the streams list cached StreamInfoList streamList = (StreamInfoList) fstate.findAttribute(DBStreamList); if ( streamList != null) return streamList; // Get the main file information and convert to stream information DBFileInfo finfo = getFileDetails(fileName,dbCtx, fstate); // Create the stream list streamList = new StreamInfoList(); StreamInfo sinfo = new StreamInfo("::$DATA", finfo.getFileId(), 0, finfo.getSize(), finfo.getAllocationSize()); streamList.addStream(sinfo); // Get the list of streams StreamInfoList sList = loadStreamList(fstate, finfo, dbCtx, true); if ( sList != null) { // Copy the stream information to the main list for ( int i = 0; i < sList.numberOfStreams(); i++) { // Add the stream to the main list streamList.addStream(sList.getStreamAt(i)); } } // Cache the stream list fstate.addAttribute(DBStreamList, streamList); // Return the stream list return streamList; } /** * Create a new stream with the specified parent file * * @param params FileOpenParams * @param parent FileState * @param dbCtx DBDeviceContext * @return NetworkFile * @exception IOException */ protected final NetworkFile createStream(FileOpenParams params, FileState parent, DBDeviceContext dbCtx) throws IOException { // Get the file information for the parent file DBFileInfo finfo = getFileDetails(params.getPath(),dbCtx,parent); if (finfo == null) throw new AccessDeniedException(); // Get the list of streams for the file StreamInfoList streamList = (StreamInfoList) parent.findAttribute(DBStreamList); if ( streamList == null) streamList = loadStreamList(parent, finfo, dbCtx, true); if ( streamList == null) throw new AccessDeniedException(); // Check if the stream already exists if ( streamList.findStream(params.getStreamName()) != null) throw new FileExistsException("Stream exists, " + params.getFullPath()); // Create a new stream record DBNetworkFile file = null; try { // Create a new stream record int stid = dbCtx.getDBInterface().createStreamRecord(params.getStreamName(), finfo.getFileId()); // Create a network file to hold details of the new stream file = (DBNetworkFile) dbCtx.getFileLoader().openFile(params, finfo.getFileId(), stid, finfo.getDirectoryId(), true, false); file.setFullName(params.getPath()); file.setStreamId(stid); file.setStreamName(params.getStreamName()); file.setDirectoryId(finfo.getDirectoryId()); file.setAttributes(params.getAttributes()); // Create a new file state for the stream FileState fstate = getFileState(params.getFullPath(), dbCtx, true); file.setFileState(fstate); fstate.setFileStatus( FileStatus.FileExists); fstate.incrementOpenCount(); // Open the stream file file.openFile(true); // Add an entry to the stream list for the new stream StreamInfo stream = new StreamInfo(params.getStreamName(), file.getFileId(), stid); streamList.addStream(stream); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("createStream() file=" + params.getPath() + ", stream=" + params.getStreamName() + ", fid/stid=" + file.getFileId() + "/" + stid); } catch (DBException ex) { if ( Debug.EnableError && hasDebug()) { Debug.println("Error: " + ex.toString()); Debug.println(ex); } } // If the file/stream is not valid throw an exception if ( file == null) throw new AccessDeniedException(params.getFullPath()); // Return the network file for the new stream return file; } /** * Open an existing stream with the specified parent file * * @param params FileOpenParams * @param parent FileState * @param dbCtx DBDeviceContext * @return NetworkFile * @exception IOException */ protected final NetworkFile openStream(FileOpenParams params, FileState parent, DBDeviceContext dbCtx) throws IOException { // Get the file information for the parent file DBFileInfo finfo = getFileDetails(params.getPath(),dbCtx,parent); if (finfo == null) throw new AccessDeniedException(); // Get the list of streams for the file StreamInfoList streamList = loadStreamList(parent, finfo, dbCtx, true); if ( streamList == null) throw new AccessDeniedException(); // Check if the stream exists StreamInfo sInfo = streamList.findStream(params.getStreamName()); if ( sInfo == null) throw new FileNotFoundException("Stream does not exist, " + params.getFullPath()); // Get, or create, a file state for the stream FileState fstate = getFileState(params.getFullPath(), dbCtx, true); // Check if the file shared access indicates exclusive file access if ( params.getSharedAccess() == SharingMode.NOSHARING && fstate.getOpenCount() > 0) throw new FileSharingException("File already open, " + params.getPath()); // Set the file information for the stream, using the stream information DBFileInfo sfinfo = new DBFileInfo(sInfo.getName(), params.getFullPath(), finfo.getFileId(), finfo.getDirectoryId()); sfinfo.setFileSize(sInfo.getSize()); fstate.addAttribute(FileState.FileInformation, sfinfo); // Create a JDBC network file and open the stream if ( Debug.EnableInfo && hasDebug()) Debug.println("DB openStream() file=" + params.getPath() + ", stream=" + sInfo.getName()); DBNetworkFile jdbcFile = (DBNetworkFile) dbCtx.getFileLoader().openFile(params, finfo.getFileId(), sInfo.getStreamId(), finfo.getDirectoryId(), false, false); jdbcFile.setFileState(fstate); jdbcFile.setFileSize(sInfo.getSize()); // Open the stream file, if not a directory jdbcFile.openFile(false); // Update the file open count fstate.setFileStatus( FileStatus.FileExists); fstate.incrementOpenCount(); // Return the network file return jdbcFile; } /** * Close an NTFS stream * * @param sess Session details * @param tree Tree connection * @param stream Network file details * @exception IOException */ protected final void closeStream(SrvSession sess, TreeConnection tree, NetworkFile stream) throws IOException { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("DB closeStream() file=" + stream.getFullName() + ", stream=" + stream.getStreamName() + ", fid/stid=" + stream.getFileId() + "/" + stream.getStreamId()); // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Close the file dbCtx.getFileLoader().closeFile(sess, stream); // Access the JDBC file DBNetworkFile jdbcFile = null; if ( stream instanceof DBNetworkFile) { // Access the JDBC file jdbcFile = (DBNetworkFile) stream; // Decrement the open file count FileState fstate = jdbcFile.getFileState(); // Check if the file state is valid, if not then check the main file state cache if ( fstate == null) { // Check the main file state cache fstate = getFileState(stream.getFullName(), dbCtx, false); } else { // Decrement the open file count for this file fstate.decrementOpenCount(); } // Check if we have a valid file state if ( fstate != null) { // Update the cached file size FileInfo finfo = (FileInfo) fstate.findAttribute(FileState.FileInformation); if ( finfo != null && stream.getWriteCount() > 0) { // Update the file size finfo.setSize(jdbcFile.getFileSize()); // Update the modified date/time finfo.setModifyDateTime(jdbcFile.getModifyDate()); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println(" Stream size=" + jdbcFile.getFileSize() + ", modifyDate=" + jdbcFile.getModifyDate()); } } } else Debug.println("closeFile() Not DBNetworkFile file=" + stream); // Check if the stream was opened for write access, if so then update the stream size if ( stream.getGrantedAccess() != NetworkFile.READONLY && stream.isDirectory() == false && stream.getWriteCount() > 0) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println(" Update stream size=" + stream.getFileSize()); // Get the current date/time long modifiedTime = 0L; if ( stream.hasModifyDate()) modifiedTime = stream.getModifyDate(); else modifiedTime = System.currentTimeMillis(); // Check if the modified time is earlier than the file creation date/time if ( stream.hasCreationDate() && modifiedTime < stream.getCreationDate()) { // Use the creation date/time for the modified date/time modifiedTime = stream.getCreationDate(); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("Close stream using creation date/time for modified date/time"); } // Update the in-memory stream information FileState parent = getFileState(stream.getFullName(), dbCtx, false); StreamInfo sInfo = null; if ( parent != null) { // Check if the stream list has been loaded StreamInfoList streamList = loadStreamList(parent, null, dbCtx, false); if ( streamList != null) { // Find the stream information sInfo = streamList.findStream(stream.getStreamName()); if ( sInfo != null) { // Update the stream size sInfo.setSize(stream.getFileSize()); // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("Updated stream file size"); } } } // Update the file details for the file stream in the database try { // Check if the file strea, details are valid if ( sInfo == null) { // Create the stream information sInfo = new StreamInfo(); sInfo.setSize(stream.getFileSize()); sInfo.setStreamId(stream.getStreamId()); } // Set the modify date/time for the stream sInfo.setModifyDateTime(System.currentTimeMillis()); // Update the stream details dbCtx.getDBInterface().setStreamInformation(stream.getDirectoryId(), stream.getFileId(), stream.getStreamId(), sInfo); } catch (DBException ex) { } } } /** * Delete a stream within a file * * @param sess SrvSession * @param tree TreeConnection * @param name String * @exception IOException */ protected final void deleteStream(SrvSession sess, TreeConnection tree, String name) throws IOException { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Parse the path string to get the directory, file name and stream name String[] paths = FileName.splitPathStream(name); // Get, or create, the file state for main file path and stream String filePath = paths[0] + paths[1]; FileState fstate = getFileState(filePath, dbCtx, true); FileState sstate = getFileState(name, dbCtx, false); // Check if the file is within an active retention period if ( fstate.hasActiveRetentionPeriod()) throw new AccessDeniedException("File retention active"); // Get the top level file information DBFileInfo finfo = getFileDetails(filePath,dbCtx, fstate); // Get the stream list for the top level file StreamInfoList streamList = (StreamInfoList) fstate.findAttribute(DBStreamList); if ( streamList == null) streamList = loadStreamList(fstate, finfo, dbCtx, true); if ( streamList == null) throw new FileNotFoundException("Stream not found, " + name); // Find the required stream details StreamInfo sInfo = streamList.findStream(paths[2]); if ( sInfo == null) throw new FileNotFoundException("Stream not found, " + name); // Delete the stream record from the database try { // Call the file loader to delete the stream data dbCtx.getFileLoader().deleteFile(name, sInfo.getFileId(), sInfo.getStreamId()); // Delete the stream record dbCtx.getDBInterface().deleteStreamRecord(sInfo.getFileId(), sInfo.getStreamId(), dbCtx.isTrashCanEnabled()); // Remove the stream information from the in memory list streamList.removeStream(sInfo.getName()); // Set the streams file state to indicate that it does not exist if ( sstate != null) sstate.setFileStatus( FileStatus.NotExist); } catch (DBException ex) { Debug.println("Error: " + ex.toString()); Debug.println(ex); } } /** * Load the stream list for the specified file * * @param fstate FileState * @param finfo DBFileInfo * @param dbCtx DBDeviceContext * @param dbLoad boolean * @return StreamInfoList */ protected final StreamInfoList loadStreamList(FileState fstate, DBFileInfo finfo, DBDeviceContext dbCtx, boolean dbLoad) { // Check if the stream list has already been loaded StreamInfoList sList = (StreamInfoList) fstate.findAttribute(FileState.StreamsList); // If the streams list is not loaded then load it from the database if ( sList == null && dbLoad == true) { // Load the streams list from the database try { // Load the streams list sList = dbCtx.getDBInterface().getStreamsList(finfo.getFileId(), DBInterface.StreamAll); } catch (DBException ex) { } } // Return the streams list return sList; } /** * Rename a stream * * @param sess SrvSession * @param tree TreeConnection * @param oldName String * @param newName String * @param overWrite boolean * @exception IOException */ public void renameStream(SrvSession sess, TreeConnection tree, String oldName, String newName, boolean overWrite) throws IOException { } /** * Return the volume information * * @param ctx DiskDeviceContext * @return VolumeInfo */ public VolumeInfo getVolumeInformation(DiskDeviceContext ctx) { // Check if the context has volume information VolumeInfo volInfo = ctx.getVolumeInformation(); if ( volInfo == null) { // Create volume information for the filesystem volInfo = new VolumeInfo(ctx.getDeviceName()); // Add to the device context ctx.setVolumeInformation(volInfo); } // Check if the serial number is valid if ( volInfo.getSerialNumber() == 0) { // Generate a random serial number volInfo.setSerialNumber(new java.util.Random().nextInt()); } // Check if the creation date is valid if ( volInfo.hasCreationDateTime() == false) { // Set the creation date to now volInfo.setCreationDateTime(new java.util.Date()); } // Return the volume information return volInfo; } /** * Return the lock manager implementation * * @param sess SrvSession * @param tree TreeConnection * @return LockManager */ public LockManager getLockManager(SrvSession sess, TreeConnection tree) { // Return the file state lock manager return _lockManager; } /** * Convert a file id to a share relative path * * @param sess SrvSession * @param tree TreeConnection * @param dirid int * @param fileid * @return String * @exception FileNotFoundException */ public String buildPathForFileId(SrvSession sess, TreeConnection tree, int dirid, int fileid) throws FileNotFoundException { // Access the JDBC context DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); // Build an array of folder names working back from the files id ArrayList names = new ArrayList(16); try { // Loop, walking backwards up the tree until we hit root int curFid = fileid; int curDid = dirid; FileInfo finfo = null; do { // Search for the current file in the database finfo = dbCtx.getDBInterface().getFileInformation(curDid, curFid, DBInterface.FileIds); if ( finfo != null) { // Get the filename names.add(finfo.getFileName()); // The directory id becomes the next file id to search for curFid = finfo.getDirectoryId(); curDid = -1; } else throw new FileNotFoundException("" + curFid); } while ( curFid > 0); } catch ( DBException ex) { Debug.println( ex); return null; } // Build the path string StringBuffer pathStr = new StringBuffer (256); pathStr.append(FileName.DOS_SEPERATOR_STR); for ( int i = names.size() - 1; i >= 0; i--) { pathStr.append(names.get(i)); pathStr.append(FileName.DOS_SEPERATOR_STR); } // Remove the trailing slash from the path if ( pathStr.length() > 0) pathStr.setLength(pathStr.length() - 1); // Return the path string return pathStr.toString(); } /** * Determine if symbolic links are enabled * * @param sess SrvSession * @param tree TreeConnection * @return boolean */ public boolean hasSymbolicLinksEnabled(SrvSession sess, TreeConnection tree) { // Access the associated database interface to check if it supports symbolic links DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); if ( dbCtx.getDBInterface().supportsFeature( DBInterface.FeatureSymLinks)) { // Database interface supports symbolic links return true; } // Symbolic links not supported return false; } /** * Read the link data for a symbolic link * * @param sess SrvSession * @param tree TreeConnection * @param path String * @return String * @exception AccessDeniedException * @exception FileNotFoundException */ public String readSymbolicLink( SrvSession sess, TreeConnection tree, String path) throws AccessDeniedException, FileNotFoundException { // Access the associated database interface to check if it supports symbolic links DBDeviceContext dbCtx = (DBDeviceContext) tree.getContext(); DBInterface dbInterface = dbCtx.getDBInterface(); String symLink = null; if ( dbInterface.supportsFeature( DBInterface.FeatureSymLinks)) { // Get, or create, the file state for the existing file FileState fstate = getFileState( path, dbCtx, true); // Get the file id of the existing file int fid = fstate.getFileId(); int dirId = -1; if ( fid == -1) { // Split the current path string and find the file id of the existing file/directory dirId = findParentDirectoryId( dbCtx, path, true); if ( dirId == -1) throw new FileNotFoundException( path); // Get the file/directory name String[] oldPaths = FileName.splitPath( path); String fname = oldPaths[1]; // Get the file id fid = getFileId( path, fname, dirId, dbCtx); if ( fid == -1) throw new FileNotFoundException( path); // Update the file state fstate.setFileId(fid); } try { // Database interface supports symbolic links, read the symbolic link symLink = dbInterface.readSymbolicLink( dirId, fid); } catch ( DBException ex) { throw new FileNotFoundException ( path); } } // Return the symbolic link data return symLink; } }