/** * Copyright (c) 2008-2016, XebiaLabs B.V., All rights reserved. * * * Overthere is licensed under the terms of the GPLv2 * <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most XebiaLabs Libraries. * There are special exceptions to the terms and conditions of the GPLv2 as it is applied to * this software, see the FLOSS License Exception * <http://github.com/xebialabs/overthere/blob/master/LICENSE>. * * 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; version 2 * of the License. * * 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 St, Fifth * Floor, Boston, MA 02110-1301 USA */ package com.xebialabs.overthere.smb; import com.hierynomus.msdtyp.AccessMask; import com.hierynomus.mserref.NtStatus; import com.hierynomus.msfscc.FileAttributes; import com.hierynomus.msfscc.fileinformation.FileInfo; import com.hierynomus.mssmb2.SMB2CreateDisposition; import com.hierynomus.smbj.common.SMBApiException; import com.hierynomus.smbj.share.DiskShare; import com.hierynomus.smbj.share.File; import com.hierynomus.smbj.transport.TransportException; import com.xebialabs.overthere.OverthereFile; import com.xebialabs.overthere.RuntimeIOException; import com.xebialabs.overthere.spi.BaseOverthereFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Map; import static java.lang.String.format; public class SmbFile extends BaseOverthereFile<SmbConnection> { private final String hostPath; private boolean overwrite = true; private Map<String, String> pathMappings; public SmbFile(SmbConnection connection, String hostPath, Map<String, String> pathMappings) { super(connection); this.hostPath = SmbPaths.escapeForwardSlashes(hostPath); this.pathMappings = pathMappings; } @Override public String getPath() { return hostPath; } @Override public String getName() { return SmbPaths.getFileName(getPathOnShare()); } @Override public OverthereFile getFile(String child) { return new SmbFile(getConnection(), SmbPaths.join(hostPath, child), pathMappings); } @Override public OverthereFile getParentFile() { OverthereFile f = null; String parentPath = SmbPaths.getParentPath(hostPath); if (parentPath != null) f = getFileForAbsolutePath(parentPath); return f; } @Override public boolean exists() { return isFile() || isDirectory(); } @Override public boolean canRead() { logger.debug("Checking whether {} can be read", this.getPath()); return getShare().checkAccessMask(AccessMask.FILE_READ_DATA, getPathOnShare()); } @Override public boolean canWrite() { logger.debug("Checking whether {} can be write", this.getPath()); return getShare().checkAccessMask(AccessMask.FILE_WRITE_DATA, getPathOnShare()); } @Override public boolean canExecute() { logger.debug("Checking whether {} can execute", this.getPath()); return getShare().checkAccessMask(AccessMask.FILE_READ_DATA, getPathOnShare()); } @Override public boolean isFile() { try { return getShare().fileExists(getPathOnShare()); } catch (SMBApiException e) { if (e.getStatus().equals(NtStatus.STATUS_FILE_IS_A_DIRECTORY) || e.getStatus().equals(NtStatus.STATUS_OBJECT_PATH_NOT_FOUND)) return false; throw new RuntimeIOException(e); } } @Override public boolean isDirectory() { try { return getShare().folderExists(getPathOnShare()); } catch (SMBApiException e) { if (e.getStatus().equals(NtStatus.STATUS_NOT_A_DIRECTORY) || e.getStatus().equals(NtStatus.STATUS_OBJECT_PATH_NOT_FOUND)) return false; throw new RuntimeIOException(e); } } @Override public boolean isHidden() { return checkAttributes(FileAttributes.FILE_ATTRIBUTE_HIDDEN); } @Override public long lastModified() { return 0; } @Override public long length() { return getShare().getFileInformation(getPathOnShare()).getFileSize(); } @Override public InputStream getInputStream() throws RuntimeIOException { logger.debug("Opening SMB input stream for {}", getSharePath()); try { final File file = getShare().openFile(getPathOnShare(), EnumSet.of(AccessMask.GENERIC_READ), SMB2CreateDisposition.FILE_OPEN); final InputStream wrapped = file.getInputStream(); return asBuffered(new InputStream() { @Override public int read() throws IOException { return wrapped.read(); } @Override public int read(byte[] b) throws IOException { return wrapped.read(b); } @Override public int read(byte[] b, int off, int len) throws IOException { return wrapped.read(b, off, len); } @Override public long skip(long n) throws IOException { return wrapped.skip(n); } @Override public void close() throws IOException { logger.debug("Closing SMB input stream for {}", getSharePath()); wrapped.close(); getShare().close(file.getFileId()); } }); } catch (TransportException e) { throw new RuntimeIOException(format("Cannot open %s for reading: %s", getSharePath(), e.toString()), e); } } @Override public OutputStream getOutputStream() { logger.debug("Opening SMB output stream for {}", getSharePath()); try { SMB2CreateDisposition createDisposition = SMB2CreateDisposition.FILE_OVERWRITE_IF; if (!overwrite) createDisposition = SMB2CreateDisposition.FILE_CREATE; final File file = getShare().openFile(getPathOnShare(), EnumSet.of(AccessMask.GENERIC_WRITE), createDisposition); final OutputStream wrapped = file.getOutputStream(); return asBuffered(new OutputStream() { @Override public void write(int b) throws IOException { wrapped.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { wrapped.write(b, off, len); } @Override public void write(byte[] b) throws IOException { wrapped.write(b); } @Override public void flush() throws IOException { wrapped.flush(); } @Override public void close() throws IOException { logger.debug("Closing SMB output stream for {}", getSharePath()); wrapped.close(); getShare().close(file.getFileId()); } }); } catch (TransportException e) { throw new RuntimeIOException(format("Cannot open %s for writing: %s", getSharePath(), e.toString()), e); } } @Override public void setExecutable(boolean executable) { // the execute permission does not exist on Windows } @Override public void delete() { delete(false); } @Override public void deleteRecursively() { delete(true); } @Override public List<OverthereFile> listFiles() { String sharePath = getPathOnShare(); logger.debug("Listing directory {}", sharePath); try { List<OverthereFile> files = new ArrayList<OverthereFile>(); for (FileInfo info : getShare().list(sharePath)) { files.add(getFile(info.getFileName())); } return files; } catch (TransportException e) { throw new RuntimeIOException(format("Cannot list directory %s: %s", sharePath, e.toString()), e); } catch (SMBApiException e) { throw new RuntimeIOException(format("Cannot list directory %s: %s", sharePath, e.toString()), e); } } @Override public void mkdir() { makeDirectory(getPathOnShare()); } private void makeDirectory(String path) { String sharePath = getPathOnShare(); logger.debug("Creating directory {}", sharePath); try { getShare().mkdir(path); } catch (TransportException e) { throw new RuntimeIOException(format("Cannot create directory %s: %s", sharePath, e.toString()), e); } catch (SMBApiException e) { throw new RuntimeIOException(format("Cannot create directory %s: %s", sharePath, e.toString()), e); } } @Override public void mkdirs() { String sharePath = getPathOnShare(); logger.debug("Creating directories {}", sharePath); String[] paths = SmbPaths.getPathListFromOuterToInner(sharePath); for (String p : paths) { if (!getShare().folderExists(p)) makeDirectory(p); } } @Override public void renameTo(OverthereFile dest) { throw new RuntimeException("Operation not supported"); } @Override public boolean equals(Object that) { if (!(that instanceof SmbFile)) { return false; } return getPath().equals(((SmbFile) that).getPath()); } @Override public int hashCode() { return getPath().hashCode(); } @Override public String toString() { return getConnection() + "/" + getPath(); } private void delete(boolean recursive) { String sharePath = getPathOnShare(); try { if (isFile()) { logger.debug("deleting file {}", sharePath); getShare().rm(sharePath); } else { logger.debug("deleting directory {}", sharePath); getShare().rmdir(sharePath, recursive); } } catch (TransportException e) { throw new RuntimeIOException(format("Cannot delete %s: %s", sharePath, e.toString()), e); } catch (SMBApiException e) { throw new RuntimeIOException(format("Cannot delete %s: %s", sharePath, e.toString()), e); } } private String getSharePath() { return SmbPaths.getSharePath(hostPath, pathMappings); } private String getPathOnShare() { return SmbPaths.getPathOnShare(getSharePath()); } private DiskShare getShare() { String shareName = SmbPaths.getShareName(getSharePath()); return connection.getShare(shareName); } private boolean checkAttributes(FileAttributes mask) { long attrMask = getShare().getFileInformation(getPathOnShare()).getFileAttributes(); return FileAttributes.EnumUtils.isSet(attrMask, mask); } private SmbFile getFileForAbsolutePath(String path) { return new SmbFile(getConnection(), path, pathMappings); } private static Logger logger = LoggerFactory.getLogger(SmbFile.class); }