jcifs.smb.SmbFile Maven / Gradle / Ivy
Show all versions of jcifs-ng Show documentation
/* jcifs smb client library in Java
* Copyright (C) 2000 "Michael B. Allen"
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcifs.smb;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jcifs.CIFSContext;
import jcifs.CIFSException;
import jcifs.CloseableIterator;
import jcifs.Configuration;
import jcifs.ResourceFilter;
import jcifs.ResourceNameFilter;
import jcifs.SmbConstants;
import jcifs.SmbFileHandle;
import jcifs.SmbResource;
import jcifs.SmbResourceLocator;
import jcifs.SmbTreeHandle;
import jcifs.SmbWatchHandle;
import jcifs.context.SingletonContext;
import jcifs.dcerpc.DcerpcHandle;
import jcifs.dcerpc.msrpc.MsrpcShareGetInfo;
import jcifs.internal.AllocInfo;
import jcifs.internal.SmbBasicFileInfo;
import jcifs.internal.dtyp.ACE;
import jcifs.internal.dtyp.SecurityDescriptor;
import jcifs.internal.dtyp.SecurityInfo;
import jcifs.internal.fscc.BasicFileInformation;
import jcifs.internal.fscc.FileBasicInfo;
import jcifs.internal.fscc.FileInformation;
import jcifs.internal.fscc.FileInternalInfo;
import jcifs.internal.fscc.FileRenameInformation2;
import jcifs.internal.fscc.FileSystemInformation;
import jcifs.internal.smb1.com.SmbComBlankResponse;
import jcifs.internal.smb1.com.SmbComCreateDirectory;
import jcifs.internal.smb1.com.SmbComDelete;
import jcifs.internal.smb1.com.SmbComDeleteDirectory;
import jcifs.internal.smb1.com.SmbComNTCreateAndX;
import jcifs.internal.smb1.com.SmbComNTCreateAndXResponse;
import jcifs.internal.smb1.com.SmbComOpenAndX;
import jcifs.internal.smb1.com.SmbComOpenAndXResponse;
import jcifs.internal.smb1.com.SmbComQueryInformation;
import jcifs.internal.smb1.com.SmbComQueryInformationResponse;
import jcifs.internal.smb1.com.SmbComRename;
import jcifs.internal.smb1.com.SmbComSeek;
import jcifs.internal.smb1.com.SmbComSeekResponse;
import jcifs.internal.smb1.com.SmbComSetInformation;
import jcifs.internal.smb1.com.SmbComSetInformationResponse;
import jcifs.internal.smb1.trans.nt.NtTransQuerySecurityDesc;
import jcifs.internal.smb1.trans.nt.NtTransQuerySecurityDescResponse;
import jcifs.internal.smb1.trans2.Trans2QueryFSInformation;
import jcifs.internal.smb1.trans2.Trans2QueryFSInformationResponse;
import jcifs.internal.smb1.trans2.Trans2QueryPathInformation;
import jcifs.internal.smb1.trans2.Trans2QueryPathInformationResponse;
import jcifs.internal.smb1.trans2.Trans2SetFileInformation;
import jcifs.internal.smb1.trans2.Trans2SetFileInformationResponse;
import jcifs.internal.smb2.ServerMessageBlock2Request;
import jcifs.internal.smb2.ServerMessageBlock2Response;
import jcifs.internal.smb2.Smb2Constants;
import jcifs.internal.smb2.create.Smb2CloseRequest;
import jcifs.internal.smb2.create.Smb2CloseResponse;
import jcifs.internal.smb2.create.Smb2CreateRequest;
import jcifs.internal.smb2.create.Smb2CreateResponse;
import jcifs.internal.smb2.info.Smb2QueryInfoRequest;
import jcifs.internal.smb2.info.Smb2QueryInfoResponse;
import jcifs.internal.smb2.info.Smb2SetInfoRequest;
/**
* This class represents a resource on an SMB network. Mainly these
* resources are files and directories however an SmbFile
* may also refer to servers and workgroups. If the resource is a file or
* directory the methods of SmbFile
follow the behavior of
* the well known {@link java.io.File} class. One fundamental difference
* is the usage of a URL scheme [1] to specify the target file or
* directory. SmbFile URLs have the following syntax:
*
*
*
*
* smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?param=value[param2=value2[...]]]
*
*
*
*
* This example:
*
*
*
*
* smb://storage15/public/foo.txt
*
*
*
*
* would reference the file foo.txt
in the share
* public
on the server storage15
. In addition
* to referencing files and directories, jCIFS can also address servers,
* and workgroups.
*
* Important: all SMB URLs that represent
* workgroups, servers, shares, or directories require a trailing slash '/'.
*
*
* When using the java.net.URL class with
* 'smb://' URLs it is necessary to first call the static
* jcifs.Config.registerSmbURLHandler(); method. This is required
* to register the SMB protocol handler.
*
* The userinfo component of the SMB URL (domain;user:pass) must
* be URL encoded if it contains reserved characters. According to RFC 2396
* these characters are non US-ASCII characters and most meta characters
* however jCIFS will work correctly with anything but '@' which is used
* to delimit the userinfo component from the server and '%' which is the
* URL escape character itself.
*
* The server
* component may a traditional NetBIOS name, a DNS name, or IP
* address. These name resolution mechanisms and their resolution order
* can be changed (See Setting Name
* Resolution Properties). The servername and path components are
* not case sensitive but the domain, username, and password components
* are. It is also likely that properties must be specified for jcifs
* to function (See Setting
* JCIFS Properties). Here are some examples of SMB URLs with brief
* descriptions of what they do:
*
*
* [1] This URL scheme is based largely on the SMB
* Filesharing URL Scheme IETF draft.
*
*
*
*
* SMB URL Examples
*
* URL
* Description
*
*
*
* smb://users-nyc;miallen:mypass@angus/tmp/
*
* This URL references a share called tmp
on the server
* angus
as user miallen
who's password is
* mypass
.
*
*
*
*
*
* smb://Administrator:P%40ss@msmith1/c/WINDOWS/Desktop/foo.txt
*
* A relativly sophisticated example that references a file
* msmith1
's desktop as user Administrator
. Notice the '@' is URL encoded with the '%40'
* hexcode escape.
*
*
*
*
* smb://angus/
*
* This references only a server. The behavior of some methods is different
* in this context(e.g. you cannot delete
a server) however
* as you might expect the list
method will list the available
* shares on this server.
*
*
*
*
* smb://myworkgroup/
*
* This syntactically is identical to the above example. However if
* myworkgroup
happends to be a workgroup(which is indeed
* suggested by the name) the list
method will return
* a list of servers that have registered themselves as members of
* myworkgroup
.
*
*
*
*
* smb://
*
* Just as smb://server/
lists shares and
* smb://workgroup/
lists servers, the smb://
* URL lists all available workgroups on a netbios LAN. Again,
* in this context many methods are not valid and return default
* values(e.g. isHidden
will always return false).
*
*
*
*
* smb://angus.foo.net/d/jcifs/pipes.doc
*
* The server name may also be a DNS name as it is in this example. See
* Setting Name Resolution Properties
* for details.
*
*
*
*
* smb://192.168.1.15/ADMIN$/
*
* The server name may also be an IP address. See Setting Name Resolution Properties
* for details.
*
*
*
*
*
* smb://domain;username:password@server/share/path/to/file.txt
*
* A prototypical example that uses all the fields.
*
*
*
*
* smb://myworkgroup/angus/ <-- ILLEGAL
*
* Despite the hierarchial relationship between workgroups, servers, and
* filesystems this example is not valid.
*
*
*
*
*
* smb://server/share/path/to/dir <-- ILLEGAL
*
* URLs that represent workgroups, servers, shares, or directories require a trailing slash '/'.
*
*
*
*
*
* smb://MYGROUP/?SERVER=192.168.10.15
*
* SMB URLs support some query string parameters. In this example
* the SERVER
parameter is used to override the
* server name service lookup to contact the server 192.168.10.15
* (presumably known to be a master
* browser) for the server list in workgroup MYGROUP
.
*
*
*
*
*
*
* A second constructor argument may be specified to augment the URL
* for better programmatic control when processing many files under
* a common base. This is slightly different from the corresponding
* java.io.File
usage; a '/' at the beginning of the second
* parameter will still use the server component of the first parameter. The
* examples below illustrate the resulting URLs when this second contructor
* argument is used.
*
*
*
*
*
* Examples Of SMB URLs When Augmented With A Second Constructor Parameter
*
*
* First Parameter
* Second Parameter
* Result
*
*
*
*
* smb://host/share/a/b/
*
*
* c/d/
*
*
* smb://host/share/a/b/c/d/
*
*
*
*
*
* smb://host/share/foo/bar/
*
*
* /share2/zig/zag
*
*
* smb://host/share2/zig/zag
*
*
*
*
*
* smb://host/share/foo/bar/
*
*
* ../zip/
*
*
* smb://host/share/foo/zip/
*
*
*
*
*
* smb://host/share/zig/zag
*
*
* smb://foo/bar/
*
*
* smb://foo/bar/
*
*
*
*
*
* smb://host/share/foo/
*
*
* ../.././.././../foo/
*
*
* smb://host/foo/
*
*
*
*
*
* smb://host/share/zig/zag
*
*
* /
*
*
* smb://host/
*
*
*
*
*
* smb://server/
*
*
* ../
*
*
* smb://server/
*
*
*
*
*
* smb://
*
*
* myworkgroup/
*
*
* smb://myworkgroup/
*
*
*
*
*
* smb://myworkgroup/
*
*
* angus/
*
*
* smb://myworkgroup/angus/ <-- ILLEGAL
(But if you first create an SmbFile with 'smb://workgroup/' and
* use and use it as the first parameter to a constructor that accepts it with a second String parameter jCIFS
* will factor out the 'workgroup'.)
*
*
*
*
*
*
* Instances of the SmbFile
class are immutable; that is,
* once created, the abstract pathname represented by an SmbFile object
* will never change.
*
* @see java.io.File
*/
public class SmbFile extends URLConnection implements SmbResource, SmbConstants {
protected static final int ATTR_GET_MASK = 0x7FFF;
protected static final int ATTR_SET_MASK = 0x30A7;
protected static final int DEFAULT_ATTR_EXPIRATION_PERIOD = 5000;
protected static final int HASH_DOT = ".".hashCode();
protected static final int HASH_DOT_DOT = "..".hashCode();
private static Logger log = LoggerFactory.getLogger(SmbFile.class);
private long createTime;
private long lastModified;
private long lastAccess;
private int attributes;
private long attrExpiration;
private long size;
private long sizeExpiration;
private boolean isExists;
private CIFSContext transportContext;
private SmbTreeConnection treeConnection;
protected final SmbResourceLocatorImpl fileLocator;
private SmbTreeHandleImpl treeHandle;
/**
* Constructs an SmbFile representing a resource on an SMB network such as
* a file or directory. See the description and examples of smb URLs above.
*
* @param url
* A URL string
* @throws MalformedURLException
* If the parent
and child
parameters
* do not follow the prescribed syntax
*/
@Deprecated
public SmbFile ( String url ) throws MalformedURLException {
this(new URL(null, url, SingletonContext.getInstance().getUrlHandler()));
}
/**
* Constructs an SmbFile representing a resource on an SMB network such
* as a file or directory from a URL object.
*
* @param url
* The URL of the target resource
*/
@Deprecated
public SmbFile ( URL url ) {
this(url, SingletonContext.getInstance().withCredentials(new NtlmPasswordAuthentication(SingletonContext.getInstance(), url.getUserInfo())));
}
/**
* Constructs an SmbFile representing a resource on an SMB network such
* as a file or directory. The second parameter is a relative path from
* the parent SmbFile
. See the description above for examples
* of using the second name
parameter.
*
* @param context
* A base SmbFile
* @param name
* A path string relative to the parent
paremeter
* @throws MalformedURLException
* If the parent
and child
parameters
* do not follow the prescribed syntax
* @throws UnknownHostException
* If the server or workgroup of the context file cannot be determined
*/
public SmbFile ( SmbResource context, String name ) throws MalformedURLException, UnknownHostException {
this(
isWorkgroup(context) ? new URL(null, "smb://" + checkName(name), context.getContext().getUrlHandler())
: new URL(context.getLocator().getURL(), checkName(name), context.getContext().getUrlHandler()),
context.getContext());
setContext(context, name);
}
/**
* Construct from string URL
*
* @param url
* @param tc
* context to use
* @throws MalformedURLException
*/
public SmbFile ( String url, CIFSContext tc ) throws MalformedURLException {
this(new URL(null, url, tc.getUrlHandler()), tc);
}
/**
* Construct from URL
*
* @param url
* @param tc
* context to use
*/
public SmbFile ( URL url, CIFSContext tc ) {
super(url);
this.transportContext = tc;
this.fileLocator = new SmbResourceLocatorImpl(tc, url);
this.treeConnection = new SmbTreeConnection(tc);
}
SmbFile ( SmbResource context, String name, boolean loadedAttributes, int type, int attributes, long createTime, long lastModified,
long lastAccess, long size ) throws MalformedURLException {
this(
isWorkgroup(context) ? new URL(null, "smb://" + checkName(name) + "/", Handler.SMB_HANDLER)
: new URL(context.getLocator().getURL(), checkName(name) + ( ( attributes & ATTR_DIRECTORY ) > 0 ? "/" : "" )),
context.getContext());
setContext(context, name + ( ( attributes & ATTR_DIRECTORY ) > 0 ? "/" : "" ));
/*
* why? am I going around in circles?
* this.type = type == TYPE_WORKGROUP ? 0 : type;
*/
this.fileLocator.updateType(type);
this.attributes = attributes;
this.createTime = createTime;
this.lastModified = lastModified;
this.lastAccess = lastAccess;
this.size = size;
this.isExists = true;
if ( loadedAttributes ) {
this.attrExpiration = this.sizeExpiration = System.currentTimeMillis() + getContext().getConfig().getAttributeCacheTimeout();
}
}
/**
* @return
*/
private static boolean isWorkgroup ( SmbResource r ) {
try {
return r.getLocator().isWorkgroup();
}
catch ( CIFSException e ) {
log.debug("Failed to check for workgroup", e);
return false;
}
}
/**
* {@inheritDoc}
*
* @see java.net.URLConnection#connect()
*/
@Override
public void connect () throws IOException {
try ( SmbTreeHandle th = ensureTreeConnected() ) {}
}
/**
*
* @return a tree handle
* @throws CIFSException
*/
public SmbTreeHandle getTreeHandle () throws CIFSException {
return ensureTreeConnected();
}
/**
* {@inheritDoc}
*
* @see java.lang.AutoCloseable#close()
*/
@Override
public synchronized void close () {
SmbTreeHandleImpl th = this.treeHandle;
if ( th != null ) {
this.treeHandle = null;
if ( this.transportContext.getConfig().isStrictResourceLifecycle() ) {
th.close();
}
}
}
/**
* @return
* @throws CIFSException
*
*/
synchronized SmbTreeHandleImpl ensureTreeConnected () throws CIFSException {
if ( this.treeHandle == null || !this.treeHandle.isConnected() ) {
if ( this.treeHandle != null && this.transportContext.getConfig().isStrictResourceLifecycle() ) {
this.treeHandle.release();
}
this.treeHandle = this.treeConnection.connectWrapException(this.fileLocator);
this.treeHandle.ensureDFSResolved();
if ( this.transportContext.getConfig().isStrictResourceLifecycle() ) {
// one extra share to keep the tree alive
return this.treeHandle.acquire();
}
return this.treeHandle;
}
return this.treeHandle.acquire();
}
/**
* @param context
* @param name
*/
private void setContext ( SmbResource context, String name ) {
this.fileLocator.resolveInContext(context.getLocator(), name);
if ( context.getLocator().getShare() != null && ( context instanceof SmbFile ) ) {
this.treeConnection = new SmbTreeConnection( ( (SmbFile) context ).treeConnection);
}
else {
this.treeConnection = new SmbTreeConnection(context.getContext());
}
}
private static String checkName ( String name ) throws MalformedURLException {
if ( name == null || name.length() == 0 ) {
throw new MalformedURLException("Name must not be empty");
}
return name;
}
/**
* @param nonPooled
* whether this file will use an exclusive connection
*/
protected void setNonPooled ( boolean nonPooled ) {
this.treeConnection.setNonPooled(nonPooled);
}
/**
* @return the transportContext
*/
@Deprecated
public CIFSContext getTransportContext () {
return this.getContext();
}
@Override
public CIFSContext getContext () {
return this.transportContext;
}
@Override
public SmbResourceLocator getLocator () {
return this.fileLocator;
}
@Override
public SmbResource resolve ( String name ) throws CIFSException {
try {
if ( name == null || name.length() == 0 ) {
throw new SmbException("Name must not be empty");
}
return new SmbFile(this, name);
}
catch (
MalformedURLException |
UnknownHostException e ) {
// this should not actually happen
throw new SmbException("Failed to resolve child element", e);
}
}
SmbFileHandleImpl openUnshared ( int flags, int access, int sharing, int attrs, int options ) throws CIFSException {
return openUnshared(getUncPath(), flags, access, sharing, attrs, options);
}
SmbFileHandleImpl openUnshared ( String uncPath, int flags, int access, int sharing, int attrs, int options ) throws CIFSException {
SmbFileHandleImpl fh = null;
try ( SmbTreeHandleImpl h = ensureTreeConnected() ) {
if ( log.isDebugEnabled() ) {
log.debug(String.format("openUnshared: %s flags: %x access: %x attrs: %x options: %x", uncPath, flags, access, attrs, options));
}
Configuration config = h.getConfig();
SmbBasicFileInfo info;
boolean haveSize = true, haveAttributes = true;
long fileSize = 0;
if ( h.isSMB2() ) {
Smb2CreateRequest req = new Smb2CreateRequest(config, uncPath);
req.setDesiredAccess(access);
if ( ( flags & SmbConstants.O_EXCL ) == O_EXCL ) {
req.setCreateDisposition(Smb2CreateRequest.FILE_CREATE);
}
else if ( ( flags & SmbConstants.O_CREAT ) == O_CREAT ) {
req.setCreateDisposition(Smb2CreateRequest.FILE_OPEN_IF);
}
else {
req.setCreateDisposition(Smb2CreateRequest.FILE_OPEN);
}
req.setShareAccess(sharing);
req.setFileAttributes(attrs);
Smb2CreateResponse resp = h.send(req);
info = resp;
fileSize = resp.getEndOfFile();
fh = new SmbFileHandleImpl(config, resp.getFileId(), h, uncPath, flags, access, 0, 0, resp.getEndOfFile());
}
else if ( h.hasCapability(SmbConstants.CAP_NT_SMBS) ) {
SmbComNTCreateAndXResponse resp = new SmbComNTCreateAndXResponse(config);
SmbComNTCreateAndX req = new SmbComNTCreateAndX(config, uncPath, flags, access, sharing, attrs, options, null);
customizeCreate(req, resp);
h.send(req, resp);
info = resp;
fileSize = resp.getEndOfFile();
this.fileLocator.updateType(resp.getFileType());
fh = new SmbFileHandleImpl(config, resp.getFid(), h, uncPath, flags, access, attrs, options, resp.getEndOfFile());
}
else {
SmbComOpenAndXResponse response = new SmbComOpenAndXResponse(config);
h.send(new SmbComOpenAndX(config, uncPath, access, sharing, flags, attrs, null), response);
this.fileLocator.updateType(response.getFileType());
info = response;
fileSize = response.getDataSize();
// this is so damn unreliable, needs another race-prone query if required
haveAttributes = false;
// This seems to be the only way to obtain a reliable (with respect to locking) file size here
// It is more critical than other attributes because append mode depends on it.
// We do only really care if we open for writing and not shared for writing
// otherwise there are no guarantees anyway, but this stuff is legacy anyways.
SmbComSeek seekReq = new SmbComSeek(config, 0);
seekReq.setMode(0x2); // from EOF
SmbComSeekResponse seekResp = new SmbComSeekResponse(config);
seekReq.setFid(response.getFid());
try {
h.send(seekReq, seekResp);
if ( log.isDebugEnabled() && seekResp.getOffset() != fileSize ) {
log.debug(String.format("Open returned wrong size %d != %d", fileSize, seekResp.getOffset()));
}
fileSize = seekResp.getOffset();
}
catch ( Exception e ) {
log.debug("Seek failed", e);
haveSize = false;
}
fh = new SmbFileHandleImpl(config, response.getFid(), h, uncPath, flags, access, 0, 0, fileSize);
}
long attrTimeout = System.currentTimeMillis() + config.getAttributeCacheTimeout();
if ( haveSize ) {
this.size = fileSize;
this.sizeExpiration = attrTimeout;
}
if ( haveAttributes ) {
this.createTime = info.getCreateTime();
this.lastModified = info.getLastWriteTime();
this.lastAccess = info.getLastAccessTime();
this.attributes = info.getAttributes() & ATTR_GET_MASK;
this.attrExpiration = attrTimeout;
}
this.isExists = true;
return fh;
}
}
/**
* @return this file's unc path below the share
*/
public String getUncPath () {
return this.fileLocator.getUNCPath();
}
/**
* @param request
* @param response
*/
protected void customizeCreate ( SmbComNTCreateAndX request, SmbComNTCreateAndXResponse response ) {}
SmbBasicFileInfo queryPath ( SmbTreeHandleImpl th, String path, int infoLevel ) throws CIFSException {
if ( log.isDebugEnabled() ) {
log.debug("queryPath: " + path);
}
/*
* We really should do the referral before this in case
* the redirected target has different capabilities. But
* the way we have been doing that is to call exists() which
* calls this method so another technique will be necessary
* to support DFS referral _to_ Win95/98/ME.
*/
if ( th.isSMB2() ) {
Smb2CreateRequest req = new Smb2CreateRequest(th.getConfig(), path);
Smb2CloseRequest closeRequest = new Smb2CloseRequest(th.getConfig(), path);
closeRequest.setCloseFlags(Smb2CloseResponse.SMB2_CLOSE_FLAG_POSTQUERY_ATTIB);
req.chain(closeRequest);
Smb2CreateResponse resp = th.send(req);
Smb2CloseResponse closeResp = closeRequest.getResponse();
if ( ( closeResp.getFlags() & Smb2CloseResponse.SMB2_CLOSE_FLAG_POSTQUERY_ATTIB ) != 0 ) {
return closeResp;
}
return resp;
}
else if ( th.hasCapability(SmbConstants.CAP_NT_SMBS) ) {
/*
* Trans2 Query Path Information Request / Response
*/
Trans2QueryPathInformationResponse response = new Trans2QueryPathInformationResponse(th.getConfig(), infoLevel);
response = th.send(new Trans2QueryPathInformation(th.getConfig(), path, infoLevel), response);
if ( log.isDebugEnabled() ) {
log.debug("Path information " + response);
}
return response.getInfo(BasicFileInformation.class);
}
/*
* Query Information Request / Response
*/
SmbComQueryInformationResponse response = new SmbComQueryInformationResponse(th.getConfig(), th.getServerTimeZoneOffset());
response = th.send(new SmbComQueryInformation(th.getConfig(), path), response);
if ( log.isDebugEnabled() ) {
log.debug("Legacy path information " + response);
}
return response;
}
@Override
public boolean exists () throws SmbException {
if ( this.attrExpiration > System.currentTimeMillis() ) {
log.trace("Using cached attributes");
return this.isExists;
}
this.attributes = ATTR_READONLY | ATTR_DIRECTORY;
this.createTime = 0L;
this.lastModified = 0L;
this.lastAccess = 0L;
this.isExists = false;
try {
if ( this.url.getHost().length() == 0 ) {}
else if ( this.fileLocator.getShare() == null ) {
if ( this.fileLocator.getType() == TYPE_WORKGROUP ) {
getContext().getNameServiceClient().getByName(this.url.getHost(), true);
}
else {
getContext().getNameServiceClient().getByName(this.url.getHost()).getHostName();
}
}
else {
// queryPath on a share root will fail, we only know whether this is one after we have resolved DFS
// referrals.
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
if ( this.fileLocator.getType() == TYPE_SHARE ) {
// treeConnect is good enough, but we need to do this after resolving DFS
try ( SmbTreeHandleImpl th2 = ensureTreeConnected() ) {}
}
else {
SmbBasicFileInfo info = queryPath(th, this.fileLocator.getUNCPath(), FileInformation.FILE_BASIC_INFO);
this.attributes = info.getAttributes();
this.createTime = info.getCreateTime();
this.lastModified = info.getLastWriteTime();
this.lastAccess = info.getLastAccessTime();
}
}
}
/*
* If any of the above fail, isExists will not be set true
*/
this.isExists = true;
}
catch ( UnknownHostException uhe ) {
log.debug("Unknown host", uhe);
}
catch ( SmbException se ) {
log.trace("exists:", se);
switch ( se.getNtStatus() ) {
case NtStatus.NT_STATUS_NO_SUCH_FILE:
case NtStatus.NT_STATUS_OBJECT_NAME_INVALID:
case NtStatus.NT_STATUS_OBJECT_NAME_NOT_FOUND:
case NtStatus.NT_STATUS_OBJECT_PATH_NOT_FOUND:
break;
default:
throw se;
}
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
this.attrExpiration = System.currentTimeMillis() + getContext().getConfig().getAttributeCacheTimeout();
return this.isExists;
}
@Override
public int getType () throws SmbException {
try {
int t = this.fileLocator.getType();
if ( t == TYPE_SHARE ) {
try ( SmbTreeHandle th = ensureTreeConnected() ) {
this.fileLocator.updateType(th.getTreeType());
}
}
return t;
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public String getName () {
return this.fileLocator.getName();
}
/**
* Everything but the last component of the URL representing this SMB
* resource is effectivly it's parent. The root URL smb://
* does not have a parent. In this case smb://
is returned.
*
* @return The parent directory of this SMB resource or
* smb://
if the resource refers to the root of the URL
* hierarchy which incedentally is also smb://
.
*/
public String getParent () {
return this.fileLocator.getParent();
}
/**
* Returns the full uncanonicalized URL of this SMB resource. An
* SmbFile
constructed with the result of this method will
* result in an SmbFile
that is equal to the original.
*
* @return The uncanonicalized full URL of this SMB resource.
*/
public String getPath () {
return this.fileLocator.getPath();
}
/**
* Retuns the Windows UNC style path with backslashs intead of forward slashes.
*
* @return The UNC path.
*/
public String getCanonicalUncPath () {
return this.fileLocator.getCanonicalURL();
}
/**
* If the path of this SmbFile
falls within a DFS volume,
* this method will return the referral path to which it maps. Otherwise
* null
is returned.
*
* @return URL to the DFS volume
* @throws SmbException
*/
public String getDfsPath () throws SmbException {
try {
String path = this.treeConnection.ensureDFSResolved(this.fileLocator).getDfsPath();
if ( isDirectory() ) {
path += '/';
}
return path;
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
/**
* Returns the full URL of this SMB resource with '.' and '..' components
* factored out. An SmbFile
constructed with the result of
* this method will result in an SmbFile
that is equal to
* the original.
*
* @return The canonicalized URL of this SMB resource.
*/
public String getCanonicalPath () {
return this.fileLocator.getCanonicalURL();
}
/**
* Retrieves the share associated with this SMB resource. In
* the case of smb://
, smb://workgroup/
,
* and smb://server/
URLs which do not specify a share,
* null
will be returned.
*
* @return The share component or null
if there is no share
*/
public String getShare () {
return this.fileLocator.getShare();
}
/**
* Retrieve the hostname of the server for this SMB resource. If the resources has been resolved by DFS this will
* return the target name.
*
* @return The server name
*/
public String getServerWithDfs () {
return this.fileLocator.getServerWithDfs();
}
/**
* Retrieve the hostname of the server for this SMB resource. If this
* SmbFile
references a workgroup, the name of the workgroup
* is returned. If this SmbFile
refers to the root of this
* SMB network hierarchy, null
is returned.
*
* @return The server or workgroup name or null
if this
* SmbFile
refers to the root smb://
resource.
*/
public String getServer () {
return this.fileLocator.getServer();
}
@Override
public SmbWatchHandle watch ( int filter, boolean recursive ) throws CIFSException {
if ( filter == 0 ) {
throw new IllegalArgumentException("filter must not be 0");
}
if ( !isDirectory() ) {
throw new SmbException("Is not a directory");
}
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
if ( !th.isSMB2() && !th.hasCapability(SmbConstants.CAP_NT_SMBS) ) {
throw new SmbUnsupportedOperationException("Not supported without CAP_NT_SMBS");
}
return new SmbWatchHandleImpl(openUnshared(O_RDONLY, READ_CONTROL | GENERIC_READ, DEFAULT_SHARING, 0, 1), filter, recursive);
}
}
@Override
public boolean canRead () throws SmbException {
if ( getType() == TYPE_NAMED_PIPE ) { // try opening the pipe for reading?
return true;
}
return exists(); // try opening and catch sharing violation?
}
@Override
public boolean canWrite () throws SmbException {
if ( getType() == TYPE_NAMED_PIPE ) { // try opening the pipe for writing?
return true;
}
return exists() && ( this.attributes & ATTR_READONLY ) == 0;
}
@Override
public boolean isDirectory () throws SmbException {
if ( this.fileLocator.isRoot() ) {
return true;
}
if ( !exists() )
return false;
return ( this.attributes & ATTR_DIRECTORY ) == ATTR_DIRECTORY;
}
@Override
public boolean isFile () throws SmbException {
if ( this.fileLocator.isRoot() ) {
return false;
}
exists();
return ( this.attributes & ATTR_DIRECTORY ) == 0;
}
@Override
public boolean isHidden () throws SmbException {
if ( this.fileLocator.getShare() == null ) {
return false;
}
else if ( this.fileLocator.isRoot() ) {
if ( this.fileLocator.getShare().endsWith("$") ) {
return true;
}
return false;
}
exists();
return ( this.attributes & ATTR_HIDDEN ) == ATTR_HIDDEN;
}
@Override
public long createTime () throws SmbException {
if ( !this.fileLocator.isRoot() ) {
exists();
return this.createTime;
}
return 0L;
}
@Override
public long lastModified () throws SmbException {
if ( !this.fileLocator.isRoot() ) {
exists();
return this.lastModified;
}
return 0L;
}
@Override
public long lastAccess () throws SmbException {
if ( !this.fileLocator.isRoot() ) {
exists();
return this.lastAccess;
}
return 0L;
}
/**
* List the contents of this SMB resource. The list returned by this
* method will be;
*
*
* - files and directories contained within this resource if the
* resource is a normal disk file directory,
*
- all available NetBIOS workgroups or domains if this resource is
* the top level URL
smb://
,
* - all servers registered as members of a NetBIOS workgroup if this
* resource refers to a workgroup in a
smb://workgroup/
URL,
* - all browseable shares of a server including printers, IPC
* services, or disk volumes if this resource is a server URL in the form
*
smb://server/
,
* - or
null
if the resource cannot be resolved.
*
*
* @return A String[]
array of files and directories,
* workgroups, servers, or shares depending on the context of the
* resource URL
* @throws SmbException
*/
public String[] list () throws SmbException {
return SmbEnumerationUtil.list(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
}
/**
* List the contents of this SMB resource. The list returned will be
* identical to the list returned by the parameterless list()
* method minus filenames filtered by the specified filter.
*
* @param filter
* a filename filter to exclude filenames from the results
* @return String[]
array of matching files and directories,
* workgroups, servers, or shares depending on the context of the
* resource URL
* @throws SmbException
* # @return An array of filenames
*/
public String[] list ( SmbFilenameFilter filter ) throws SmbException {
return SmbEnumerationUtil.list(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null);
}
/**
* List the contents of this SMB resource as an array of
* SmbResource
objects. This method is much more efficient than
* the regular list
method when querying attributes of each
* file in the result set.
*
* The list of SmbResource
s returned by this method will be;
*
*
* - files and directories contained within this resource if the
* resource is a normal disk file directory,
*
- all available NetBIOS workgroups or domains if this resource is
* the top level URL
smb://
,
* - all servers registered as members of a NetBIOS workgroup if this
* resource refers to a workgroup in a
smb://workgroup/
URL,
* - all browseable shares of a server including printers, IPC
* services, or disk volumes if this resource is a server URL in the form
*
smb://server/
,
* - or
null
if the resource cannot be resolved.
*
*
* If strict resource lifecycle is used, make sure you close the individual files after use.
*
* @return An array of SmbResource
objects representing file
* and directories, workgroups, servers, or shares depending on the context
* of the resource URL
* @throws SmbException
*/
public SmbFile[] listFiles () throws SmbException {
return SmbEnumerationUtil.listFiles(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
}
/**
* The CIFS protocol provides for DOS "wildcards" to be used as
* a performance enhancement. The client does not have to filter
* the names and the server does not have to return all directory
* entries.
*
* The wildcard expression may consist of two special meta
* characters in addition to the normal filename characters. The '*'
* character matches any number of characters in part of a name. If
* the expression begins with one or more '?'s then exactly that
* many characters will be matched whereas if it ends with '?'s
* it will match that many characters or less.
*
* Wildcard expressions will not filter workgroup names or server names.
*
*
*
*
* winnt> ls c?o*
* clock.avi -rw-- 82944 Mon Oct 14 1996 1:38 AM
* Cookies drw-- 0 Fri Nov 13 1998 9:42 PM
* 2 items in 5ms
*
*
*
*
* If strict resource lifecycle is used, make sure you close the individual files after use.
*
* @param wildcard
* a wildcard expression
* @throws SmbException
* @return An array of SmbResource
objects representing file
* and directories, workgroups, servers, or shares depending on the context
* of the resource URL
*/
public SmbFile[] listFiles ( String wildcard ) throws SmbException {
return SmbEnumerationUtil.listFiles(this, wildcard, ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
}
/**
* List the contents of this SMB resource. The list returned will be
* identical to the list returned by the parameterless listFiles()
* method minus files filtered by the specified filename filter.
*
* If strict resource lifecycle is used, make sure you close the individual files after use.
*
* @param filter
* a filter to exclude files from the results
* @return An array of SmbResource objects
* @throws SmbException
*/
public SmbFile[] listFiles ( SmbFilenameFilter filter ) throws SmbException {
return SmbEnumerationUtil.listFiles(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null);
}
/**
* List the contents of this SMB resource. The list returned will be
* identical to the list returned by the parameterless listFiles()
* method minus filenames filtered by the specified filter.
*
* If strict resource lifecycle is used, make sure you close the individual files after use.
*
* @param filter
* a file filter to exclude files from the results
* @return An array of SmbResource objects
* @throws SmbException
*/
public SmbFile[] listFiles ( SmbFileFilter filter ) throws SmbException {
return SmbEnumerationUtil.listFiles(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, filter);
}
@Override
public CloseableIterator children () throws CIFSException {
return SmbEnumerationUtil.doEnum(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
}
@Override
public CloseableIterator children ( String wildcard ) throws CIFSException {
return SmbEnumerationUtil.doEnum(this, wildcard, ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null);
}
@Override
public CloseableIterator children ( ResourceNameFilter filter ) throws CIFSException {
return SmbEnumerationUtil.doEnum(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null);
}
@Override
public CloseableIterator children ( ResourceFilter filter ) throws CIFSException {
return SmbEnumerationUtil.doEnum(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, filter);
}
@Override
public void renameTo ( SmbResource d ) throws SmbException {
renameTo(d, false);
}
@Override
public void renameTo ( SmbResource d, boolean replace ) throws SmbException {
if ( ! ( d instanceof SmbFile ) ) {
throw new SmbException("Invalid target resource");
}
SmbFile dest = (SmbFile) d;
try ( SmbTreeHandleImpl sh = ensureTreeConnected();
SmbTreeHandleImpl th = dest.ensureTreeConnected() ) {
// this still might be required for standalone DFS
exists();
dest.exists();
if ( this.fileLocator.isRoot() || dest.getLocator().isRoot() ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
if ( !sh.isSameTree(th) ) {
// trigger requests to resolve the actual target
exists();
dest.exists();
if ( !sh.isSameTree(th) ) {
throw new SmbException("Cannot rename between different trees");
}
}
if ( log.isDebugEnabled() ) {
log.debug("renameTo: " + getUncPath() + " -> " + dest.getUncPath());
}
this.attrExpiration = this.sizeExpiration = 0;
dest.attrExpiration = 0;
/*
* Rename Request / Response
*/
if ( sh.isSMB2() ) {
Smb2SetInfoRequest req = new Smb2SetInfoRequest(sh.getConfig());
req.setFileInformation(new FileRenameInformation2(dest.getUncPath().substring(1), replace));
withOpen(sh, Smb2CreateRequest.FILE_OPEN, FILE_WRITE_ATTRIBUTES | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE, req);
}
else {
if ( replace ) {
// TRANS2_SET_FILE_INFORMATION does not seem to support the SMB1 RENAME_INFO
throw new SmbUnsupportedOperationException("Replacing rename only supported with SMB2");
}
sh.send(new SmbComRename(sh.getConfig(), getUncPath(), dest.getUncPath()), new SmbComBlankResponse(sh.getConfig()));
}
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
void copyRecursive ( SmbFile dest, byte[][] b, int bsize, WriterThread w, SmbTreeHandleImpl sh, SmbTreeHandleImpl dh ) throws CIFSException {
if ( this.attrExpiration < System.currentTimeMillis() ) {
this.attributes = ATTR_READONLY | ATTR_DIRECTORY;
this.createTime = 0L;
this.lastModified = 0L;
this.isExists = false;
SmbBasicFileInfo info = queryPath(sh, this.fileLocator.getUNCPath(), FileInformation.FILE_BASIC_INFO);
this.attributes = info.getAttributes();
this.createTime = info.getCreateTime();
this.lastModified = info.getLastWriteTime();
/*
* If any of the above fails, isExists will not be set true
*/
this.isExists = true;
this.attrExpiration = System.currentTimeMillis() + getContext().getConfig().getAttributeCacheTimeout();
}
if ( isDirectory() ) {
SmbCopyUtil.copyDir(this, dest, b, bsize, w, sh, dh);
}
else {
SmbCopyUtil.copyFile(this, dest, b, bsize, w, sh, dh);
}
dest.clearAttributeCache();
}
/**
*
*/
void clearAttributeCache () {
this.attrExpiration = 0;
this.sizeExpiration = 0;
}
@Override
public void copyTo ( SmbResource d ) throws SmbException {
if ( ! ( d instanceof SmbFile ) ) {
throw new SmbException("Invalid target resource");
}
SmbFile dest = (SmbFile) d;
try ( SmbTreeHandleImpl sh = ensureTreeConnected();
SmbTreeHandleImpl dh = dest.ensureTreeConnected() ) {
/*
* Should be able to copy an entire share actually
*/
if ( this.fileLocator.getShare() == null || dest.getLocator().getShare() == null ) {
throw new SmbException("Invalid operation for workgroups or servers");
}
/*
* It is invalid for the source path to be a child of the destination
* path or visa versa.
*/
if ( this.fileLocator.overlaps(dest.getLocator()) ) {
throw new SmbException("Source and destination paths overlap.");
}
WriterThread w = new WriterThread();
w.setDaemon(true);
try {
w.start();
// use commonly acceptable buffer size
int bsize = Math.min(sh.getReceiveBufferSize() - 70, dh.getSendBufferSize() - 70);
byte[][] b = new byte[2][bsize];
copyRecursive(dest, b, bsize, w, sh, dh);
}
finally {
w.write(null, -1, null);
w.interrupt();
try {
w.join();
}
catch ( InterruptedException e ) {
log.warn("Interrupted while joining copy thread", e);
}
}
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public void delete () throws SmbException {
exists();
try {
delete(this.fileLocator.getUNCPath());
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
close();
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize () throws Throwable {
if ( this.treeHandle != null ) {
log.debug("File was not properly released " + this);
}
}
void delete ( String fileName ) throws CIFSException {
if ( this.fileLocator.isRoot() ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
if ( System.currentTimeMillis() > this.attrExpiration ) {
this.attributes = ATTR_READONLY | ATTR_DIRECTORY;
this.createTime = 0L;
this.lastModified = 0L;
this.lastAccess = 0L;
this.isExists = false;
SmbBasicFileInfo info = queryPath(th, this.fileLocator.getUNCPath(), FileInformation.FILE_BASIC_INFO);
this.attributes = info.getAttributes();
this.createTime = info.getCreateTime();
this.lastModified = info.getLastWriteTime();
this.lastAccess = info.getLastAccessTime();
this.attrExpiration = System.currentTimeMillis() + getContext().getConfig().getAttributeCacheTimeout();
this.isExists = true;
}
if ( ( this.attributes & ATTR_READONLY ) != 0 ) {
setReadWrite();
}
/*
* Delete or Delete Directory Request / Response
*/
if ( log.isDebugEnabled() ) {
log.debug("delete: " + fileName);
}
if ( ( this.attributes & ATTR_DIRECTORY ) != 0 ) {
/*
* Recursively delete directory contents
*/
try ( CloseableIterator it = SmbEnumerationUtil
.doEnum(this, "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null) ) {
while ( it.hasNext() ) {
try ( SmbResource r = it.next() ) {
try {
r.delete();
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
}
}
catch ( SmbException se ) {
/*
* Oracle FilesOnline version 9.0.4 doesn't send '.' and '..' so
* listFiles may generate undesireable "cannot find
* the file specified".
*/
log.debug("delete", se);
if ( se.getNtStatus() != NtStatus.NT_STATUS_NO_SUCH_FILE ) {
throw se;
}
}
if ( th.isSMB2() ) {
Smb2CreateRequest req = new Smb2CreateRequest(th.getConfig(), fileName);
req.setDesiredAccess(0x10000); // delete
req.setCreateOptions(Smb2CreateRequest.FILE_DELETE_ON_CLOSE | Smb2CreateRequest.FILE_DIRECTORY_FILE);
req.setCreateDisposition(Smb2CreateRequest.FILE_OPEN);
req.chain(new Smb2CloseRequest(th.getConfig(), fileName));
th.send(req);
}
else {
th.send(new SmbComDeleteDirectory(th.getConfig(), fileName), new SmbComBlankResponse(th.getConfig()));
}
}
else {
if ( th.isSMB2() ) {
Smb2CreateRequest req = new Smb2CreateRequest(th.getConfig(), fileName.substring(1));
req.setDesiredAccess(0x10000); // delete
req.setCreateOptions(Smb2CreateRequest.FILE_DELETE_ON_CLOSE);
req.chain(new Smb2CloseRequest(th.getConfig(), fileName));
th.send(req);
}
else {
th.send(new SmbComDelete(th.getConfig(), fileName), new SmbComBlankResponse(th.getConfig()));
}
}
this.attrExpiration = this.sizeExpiration = 0;
}
}
@Override
public long length () throws SmbException {
if ( this.sizeExpiration > System.currentTimeMillis() ) {
return this.size;
}
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
int t = getType();
if ( t == TYPE_SHARE ) {
this.size = fetchAllocationInfo(th).getCapacity();
}
else if ( !this.fileLocator.isRoot() && t != TYPE_NAMED_PIPE ) {
SmbBasicFileInfo info = queryPath(th, this.fileLocator.getUNCPath(), FileInformation.FILE_STANDARD_INFO);
this.size = info.getSize();
}
else {
this.size = 0L;
}
this.sizeExpiration = System.currentTimeMillis() + getContext().getConfig().getAttributeCacheTimeout();
return this.size;
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public long getDiskFreeSpace () throws SmbException {
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
int t = getType();
if ( t == TYPE_SHARE || t == TYPE_FILESYSTEM ) {
AllocInfo allocInfo = fetchAllocationInfo(th);
this.size = allocInfo.getCapacity();
this.sizeExpiration = System.currentTimeMillis() + getContext().getConfig().getAttributeCacheTimeout();
return allocInfo.getFree();
}
return 0L;
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
/**
* @return
* @throws CIFSException
* @throws SmbException
*/
private AllocInfo fetchAllocationInfo ( SmbTreeHandleImpl th ) throws CIFSException, SmbException {
AllocInfo ai;
try {
ai = queryFSInformation(th, AllocInfo.class, FileSystemInformation.FS_SIZE_INFO);
}
catch ( SmbException ex ) {
log.debug("getDiskFreeSpace", ex);
switch ( ex.getNtStatus() ) {
case NtStatus.NT_STATUS_INVALID_INFO_CLASS:
case NtStatus.NT_STATUS_UNSUCCESSFUL: // NetApp Filer
if ( !th.isSMB2() ) {
// SMB_FS_FULL_SIZE_INFORMATION not supported by the server.
ai = queryFSInformation(th, AllocInfo.class, FileSystemInformation.SMB_INFO_ALLOCATION);
break;
}
default:
throw ex;
}
}
return ai;
}
private T queryFSInformation ( SmbTreeHandleImpl th, Class clazz, byte level ) throws CIFSException {
if ( th.isSMB2() ) {
Smb2QueryInfoRequest qreq = new Smb2QueryInfoRequest(th.getConfig());
qreq.setFilesystemInfoClass(level);
Smb2QueryInfoResponse resp = withOpen(th, qreq);
return resp.getInfo(clazz);
}
Trans2QueryFSInformationResponse response = new Trans2QueryFSInformationResponse(th.getConfig(), level);
th.send(new Trans2QueryFSInformation(th.getConfig(), level), response);
return response.getInfo(clazz);
}
@Override
public void mkdir () throws SmbException {
String path = this.fileLocator.getUNCPath();
if ( path.length() == 1 ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
// should not normally be required, but samba without NTStatus does not properly resolve the path and fails
// with
// STATUS_UNSUCCESSFUL
exists();
// get the path again, this may have changed through DFS referrals
path = this.fileLocator.getUNCPath();
/*
* Create Directory Request / Response
*/
if ( log.isDebugEnabled() ) {
log.debug("mkdir: " + path);
}
if ( th.isSMB2() ) {
Smb2CreateRequest req = new Smb2CreateRequest(th.getConfig(), path);
req.setCreateDisposition(Smb2CreateRequest.FILE_CREATE);
req.setCreateOptions(Smb2CreateRequest.FILE_DIRECTORY_FILE);
req.chain(new Smb2CloseRequest(th.getConfig(), path));
th.send(req);
}
else {
th.send(new SmbComCreateDirectory(th.getConfig(), path), new SmbComBlankResponse(th.getConfig()));
}
this.attrExpiration = this.sizeExpiration = 0;
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public void mkdirs () throws SmbException {
String p = this.fileLocator.getParent();
try ( SmbTreeHandle th = ensureTreeConnected();
SmbFile parent = new SmbFile(p, getContext()) ) {
try {
if ( !parent.exists() ) {
if ( log.isDebugEnabled() ) {
log.debug("Parent does not exist " + p);
}
parent.mkdirs();
}
}
catch ( SmbException e ) {
if ( log.isDebugEnabled() ) {
log.debug("Failed to ensure parent exists " + p, e);
}
throw e;
}
try {
mkdir();
}
catch ( SmbException e ) {
log.debug("mkdirs", e);
// Ignore "Cannot create a file when that file already exists." errors for now as
// they seem to be show up under some cinditions most likely due to timing issues.
if ( e.getNtStatus() != NtStatus.NT_STATUS_OBJECT_NAME_COLLISION ) {
throw e;
}
}
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
catch ( MalformedURLException e ) {
throw new SmbException("Invalid URL in mkdirs", e);
}
}
protected T withOpen ( SmbTreeHandleImpl th, ServerMessageBlock2Request first,
ServerMessageBlock2Request>... others ) throws CIFSException {
return withOpen(th, Smb2CreateRequest.FILE_OPEN, 0x00120089, SmbConstants.FILE_SHARE_READ | SmbConstants.FILE_SHARE_WRITE, first, others);
}
protected T withOpen ( SmbTreeHandleImpl th, int createDisposition, int desiredAccess, int shareAccess,
ServerMessageBlock2Request first, ServerMessageBlock2Request>... others ) throws CIFSException {
return withOpen(th, createDisposition, 0, SmbConstants.ATTR_NORMAL, desiredAccess, shareAccess, first, others);
}
@SuppressWarnings ( "unchecked" )
protected T withOpen ( SmbTreeHandleImpl th, int createDisposition, int createOptions, int fileAttributes,
int desiredAccess, int shareAccess, ServerMessageBlock2Request first, ServerMessageBlock2Request>... others ) throws CIFSException {
Smb2CreateRequest cr = new Smb2CreateRequest(th.getConfig(), getUncPath());
try {
cr.setCreateDisposition(createDisposition);
cr.setCreateOptions(createOptions);
cr.setFileAttributes(fileAttributes);
cr.setDesiredAccess(desiredAccess);
cr.setShareAccess(shareAccess);
ServerMessageBlock2Request> cur = cr;
if ( first != null ) {
cr.chain(first);
cur = first;
for ( ServerMessageBlock2Request> req : others ) {
cur.chain(req);
cur = req;
}
}
Smb2CloseRequest closeReq = new Smb2CloseRequest(th.getConfig(), getUncPath());
closeReq.setCloseFlags(Smb2CloseResponse.SMB2_CLOSE_FLAG_POSTQUERY_ATTIB);
cur.chain(closeReq);
Smb2CreateResponse createResp = th.send(cr);
Smb2CloseResponse closeResp = closeReq.getResponse();
SmbBasicFileInfo info;
if ( ( closeResp.getCloseFlags() & Smb2CloseResponse.SMB2_CLOSE_FLAG_POSTQUERY_ATTIB ) != 0 ) {
info = closeResp;
}
else {
info = createResp;
}
this.createTime = info.getCreateTime();
this.lastModified = info.getLastWriteTime();
this.lastAccess = info.getLastAccessTime();
this.size = info.getSize();
this.attributes = info.getAttributes() & ATTR_GET_MASK;
this.attrExpiration = System.currentTimeMillis() + th.getConfig().getAttributeCacheTimeout();
this.isExists = true;
return (T) createResp.getNextResponse();
}
catch (
CIFSException |
RuntimeException e ) {
try {
// make sure that the handle is closed when one of the requests fails
Smb2CreateResponse createResp = cr.getResponse();
if ( createResp.isReceived() && createResp.getStatus() == NtStatus.NT_STATUS_OK ) {
th.send(new Smb2CloseRequest(th.getConfig(), createResp.getFileId()), RequestParam.NO_RETRY);
}
}
catch ( Exception e2 ) {
log.debug("Failed to close after failure", e2);
e.addSuppressed(e2);
}
throw e;
}
}
@Override
public void createNewFile () throws SmbException {
if ( this.fileLocator.isRoot() ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
if ( th.isSMB2() ) {
withOpen(th, Smb2CreateRequest.FILE_OPEN_IF, O_RDWR, 0, null);
}
else {
try ( SmbFileHandle fd = openUnshared(O_RDWR | O_CREAT | O_EXCL, O_RDWR, FILE_NO_SHARE, ATTR_NORMAL, 0) ) {
// close explicitly
fd.close(0L);
}
}
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
void setPathInformation ( int attrs, long ctime, long mtime, long atime ) throws CIFSException {
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
exists();
int dir = this.attributes & ATTR_DIRECTORY;
if ( th.isSMB2() ) {
Smb2SetInfoRequest req = new Smb2SetInfoRequest(th.getConfig());
req.setFileInformation(new FileBasicInfo(ctime, atime, mtime, 0L, attrs | dir));
withOpen(th, Smb2CreateRequest.FILE_OPEN, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, req);
}
else if ( th.hasCapability(SmbConstants.CAP_NT_SMBS) ) {
try ( SmbFileHandleImpl f = openUnshared(
O_RDONLY,
FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE,
dir,
dir != 0 ? 0x0001 : 0x0040) ) {
th.send(
new Trans2SetFileInformation(th.getConfig(), f.getFid(), attrs | dir, ctime, mtime, atime),
new Trans2SetFileInformationResponse(th.getConfig()),
RequestParam.NO_RETRY);
}
}
else {
if ( ctime != 0 || atime != 0 ) {
throw new SmbUnsupportedOperationException("Cannot set creation or access time without CAP_NT_SMBS");
}
th.send(
new SmbComSetInformation(th.getConfig(), getUncPath(), attrs, mtime - th.getServerTimeZoneOffset()),
new SmbComSetInformationResponse(th.getConfig()));
}
this.attrExpiration = 0;
}
}
@Override
public void setCreateTime ( long time ) throws SmbException {
if ( this.fileLocator.isRoot() ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
try {
setPathInformation(0, time, 0L, 0L);
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public void setLastModified ( long time ) throws SmbException {
if ( this.fileLocator.isRoot() ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
try {
setPathInformation(0, 0L, time, 0L);
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public void setLastAccess ( long time ) throws SmbException {
if ( this.fileLocator.isRoot() ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
try {
setPathInformation(0, 0L, 0L, time);
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public int getAttributes () throws SmbException {
if ( this.fileLocator.isRoot() ) {
return 0;
}
exists();
return this.attributes & ATTR_GET_MASK;
}
@Override
public void setAttributes ( int attrs ) throws SmbException {
if ( this.fileLocator.isRoot() ) {
throw new SmbException("Invalid operation for workgroups, servers, or shares");
}
try {
setPathInformation(attrs & ATTR_SET_MASK, 0L, 0L, 0L);
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
}
@Override
public void setReadOnly () throws SmbException {
setAttributes(getAttributes() | ATTR_READONLY);
}
@Override
public void setReadWrite () throws SmbException {
setAttributes(getAttributes() & ~ATTR_READONLY);
}
/**
* Returns a {@link java.net.URL} for this SmbFile
. The
* URL
may be used as any other URL
might to
* access an SMB resource. Currently only retrieving data and information
* is supported (i.e. no doOutput).
*
* @deprecated Use getURL() instead
* @return A new {@link java.net.URL}
for this SmbFile
*/
@Deprecated
public URL toURL () {
return getURL();
}
/**
* Computes a hashCode for this file based on the URL string and IP
* address if the server. The hashing function uses the hashcode of the
* server address, the canonical representation of the URL, and does not
* compare authentication information. In essance, two
* SmbFile
objects that refer to
* the same file should generate the same hashcode provided it is possible
* to make such a determination.
*
* @return A hashcode for this abstract file
*/
@Override
public int hashCode () {
return this.fileLocator.hashCode();
}
/**
* Tests to see if two SmbFile
objects are equal. Two
* SmbFile objects are equal when they reference the same SMB
* resource. More specifically, two SmbFile
objects are
* equals if their server IP addresses are equal and the canonicalized
* representation of their URLs, minus authentication parameters, are
* case insensitivly and lexographically equal.
*
* For example, assuming the server angus
resolves to the
* 192.168.1.15
IP address, the below URLs would result in
* SmbFile
s that are equal.
*
*
*
*
*
* smb://192.168.1.15/share/DIR/foo.txt
* smb://angus/share/data/../dir/foo.txt
*
*
*
*
* @param obj
* Another SmbFile
object to compare for equality
* @return true
if the two objects refer to the same SMB resource
* and false
otherwise
*/
@Override
public boolean equals ( Object obj ) {
if ( obj instanceof SmbFile ) {
SmbResource f = (SmbResource) obj;
if ( this == f )
return true;
return this.fileLocator.equals(f.getLocator());
}
return false;
}
/**
* Returns the string representation of this SmbFile object. This will
* be the same as the URL used to construct this SmbFile
.
* This method will return the same value
* as getPath
.
*
* @return The original URL representation of this SMB resource
*/
@Override
public String toString () {
return this.url.toString();
}
/* URLConnection implementation */
/**
* This URLConnection method just returns the result of length().
*
* @return the length of this file or 0 if it refers to a directory
*/
@Deprecated
@Override
public int getContentLength () {
try {
return (int) ( length() & 0xFFFFFFFFL );
}
catch ( SmbException se ) {
log.debug("getContentLength", se);
}
return 0;
}
/**
* {@inheritDoc}
*
* @see java.net.URLConnection#getContentLengthLong()
*/
@Override
public long getContentLengthLong () {
try {
return length();
}
catch ( SmbException se ) {
log.debug("getContentLength", se);
}
return 0;
}
/**
* This URLConnection method just returns the result of lastModified.
*
* @return the last modified data as milliseconds since Jan 1, 1970
*/
@Override
public long getDate () {
try {
return lastModified();
}
catch ( SmbException se ) {
log.debug("getDate", se);
}
return 0L;
}
/**
* This URLConnection method just returns the result of lastModified.
*
* @return the last modified data as milliseconds since Jan 1, 1970
*/
@Override
public long getLastModified () {
try {
return lastModified();
}
catch ( SmbException se ) {
log.debug("getLastModified", se);
}
return 0L;
}
/**
* This URLConnection method just returns a new SmbFileInputStream created with this file.
*
* @throws IOException
* thrown by SmbFileInputStream constructor
*/
@Override
public InputStream getInputStream () throws IOException {
return new SmbFileInputStream(this);
}
@Override
public SmbFileInputStream openInputStream () throws SmbException {
return new SmbFileInputStream(this);
}
@Override
public SmbFileInputStream openInputStream ( int sharing ) throws SmbException {
return openInputStream(0, O_RDONLY, sharing);
}
@Override
public SmbFileInputStream openInputStream ( int flags, int access, int sharing ) throws SmbException {
return new SmbFileInputStream(this, flags, access, sharing, false);
}
@Override
public OutputStream getOutputStream () throws IOException {
return new SmbFileOutputStream(this);
}
@Override
public SmbFileOutputStream openOutputStream () throws SmbException {
return new SmbFileOutputStream(this);
}
@Override
public SmbFileOutputStream openOutputStream ( boolean append ) throws SmbException {
return openOutputStream(append, FILE_SHARE_READ);
}
@Override
public SmbFileOutputStream openOutputStream ( boolean append, int sharing ) throws SmbException {
return openOutputStream(append, append ? O_CREAT | O_WRONLY | O_APPEND : O_CREAT | O_WRONLY | O_TRUNC, 0, sharing);
}
@Override
public SmbFileOutputStream openOutputStream ( boolean append, int openFlags, int access, int sharing ) throws SmbException {
return new SmbFileOutputStream(this, append, openFlags, access, sharing);
}
@Override
public SmbRandomAccessFile openRandomAccess ( String mode ) throws SmbException {
return new SmbRandomAccessFile(this, mode);
}
@Override
public SmbRandomAccessFile openRandomAccess ( String mode, int sharing ) throws SmbException {
return new SmbRandomAccessFile(this, mode, sharing, false);
}
private void processAces ( ACE[] aces, boolean resolveSids ) throws IOException {
String server = this.fileLocator.getServerWithDfs();
int ai;
if ( resolveSids ) {
SID[] sids = new SID[aces.length];
for ( ai = 0; ai < aces.length; ai++ ) {
sids[ ai ] = aces[ ai ].getSID();
}
for ( int off = 0; off < sids.length; off += 64 ) {
int len = sids.length - off;
if ( len > 64 )
len = 64;
getContext().getSIDResolver().resolveSids(getContext(), server, sids, off, len);
}
}
else {
for ( ai = 0; ai < aces.length; ai++ ) {
aces[ ai ].getSID().origin_server = server;
aces[ ai ].getSID().origin_ctx = getContext();
}
}
}
@Override
public long fileIndex () throws SmbException {
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
if ( th.isSMB2() ) {
Smb2QueryInfoRequest req = new Smb2QueryInfoRequest(th.getConfig());
req.setFileInfoClass(FileInformation.FILE_INTERNAL_INFO);
Smb2QueryInfoResponse resp = withOpen(th, req);
FileInternalInfo info = resp.getInfo(FileInternalInfo.class);
return info.getIndexNumber();
}
}
catch ( CIFSException e ) {
throw SmbException.wrap(e);
}
return 0;
}
SecurityDescriptor querySecurity ( SmbTreeHandleImpl th, int types ) throws CIFSException {
if ( th.isSMB2() ) {
Smb2QueryInfoRequest req = new Smb2QueryInfoRequest(th.getConfig());
req.setInfoType(Smb2Constants.SMB2_0_INFO_SECURITY);
req.setAdditionalInformation(types);
Smb2QueryInfoResponse resp = withOpen(th, req);
return resp.getInfo(SecurityDescriptor.class);
}
if ( !th.hasCapability(SmbConstants.CAP_NT_SMBS) ) {
throw new SmbUnsupportedOperationException("Not supported without CAP_NT_SMBS/SMB2");
}
NtTransQuerySecurityDescResponse response = new NtTransQuerySecurityDescResponse(getContext().getConfig());
try ( SmbFileHandleImpl f = openUnshared(O_RDONLY, READ_CONTROL, DEFAULT_SHARING, 0, isDirectory() ? 1 : 0) ) {
/*
* NtTrans Query Security Desc Request / Response
*/
NtTransQuerySecurityDesc request = new NtTransQuerySecurityDesc(getContext().getConfig(), f.getFid(), types);
response = th.send(request, response, RequestParam.NO_RETRY);
return response.getSecurityDescriptor();
}
}
@Override
public ACE[] getSecurity () throws IOException {
return getSecurity(false);
}
@Override
public ACE[] getSecurity ( boolean resolveSids ) throws IOException {
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
SecurityDescriptor desc = querySecurity(th, SecurityInfo.DACL_SECURITY_INFO);
ACE[] aces = desc.getAces();
if ( aces != null ) {
processAces(aces, resolveSids);
}
return aces;
}
}
@Override
public SID getOwnerUser () throws IOException {
return getOwnerUser(true);
}
@Override
public SID getOwnerUser ( boolean resolve ) throws IOException {
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
SecurityDescriptor desc = querySecurity(th, SecurityInfo.OWNER_SECURITY_INFO);
SID ownerUser = desc.getOwnerUserSid();
if ( ownerUser == null ) {
return null;
}
if ( resolve ) {
ownerUser.resolve(this.fileLocator.getServerWithDfs(), getContext());
}
return ownerUser;
}
}
@Override
public SID getOwnerGroup () throws IOException {
return getOwnerGroup(true);
}
@Override
public SID getOwnerGroup ( boolean resolve ) throws IOException {
try ( SmbTreeHandleImpl th = ensureTreeConnected() ) {
SecurityDescriptor desc = querySecurity(th, SecurityInfo.GROUP_SECURITY_INFO);
SID ownerGroup = desc.getOwnerGroupSid();
if ( ownerGroup == null ) {
return null;
}
if ( resolve ) {
ownerGroup.resolve(this.fileLocator.getServerWithDfs(), getContext());
}
return ownerGroup;
}
}
@Override
public ACE[] getShareSecurity ( boolean resolveSids ) throws IOException {
try ( SmbTreeHandleInternal th = ensureTreeConnected() ) {
String server = this.fileLocator.getServerWithDfs();
ACE[] aces;
MsrpcShareGetInfo rpc = new MsrpcShareGetInfo(server, th.getConnectedShare());
try ( DcerpcHandle handle = DcerpcHandle.getHandle("ncacn_np:" + server + "[\\PIPE\\srvsvc]", getContext()) ) {
handle.sendrecv(rpc);
if ( rpc.retval != 0 )
throw new SmbException(rpc.retval, true);
aces = rpc.getSecurity();
if ( aces != null )
processAces(aces, resolveSids);
}
return aces;
}
}
}