jcifs.smb.SmbTreeImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcifs-ng Show documentation
Show all versions of jcifs-ng Show documentation
A pure-java CIFS/SMB client library
/* 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.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jcifs.CIFSContext;
import jcifs.CIFSException;
import jcifs.DfsReferralData;
import jcifs.RuntimeCIFSException;
import jcifs.SmbConstants;
import jcifs.SmbTree;
import jcifs.internal.CommonServerMessageBlockRequest;
import jcifs.internal.CommonServerMessageBlockResponse;
import jcifs.internal.RequestWithPath;
import jcifs.internal.SmbNegotiationResponse;
import jcifs.internal.TreeConnectResponse;
import jcifs.internal.smb1.ServerMessageBlock;
import jcifs.internal.smb1.com.SmbComBlankResponse;
import jcifs.internal.smb1.com.SmbComNegotiateResponse;
import jcifs.internal.smb1.com.SmbComTreeConnectAndX;
import jcifs.internal.smb1.com.SmbComTreeConnectAndXResponse;
import jcifs.internal.smb1.com.SmbComTreeDisconnect;
import jcifs.internal.smb1.trans.SmbComTransaction;
import jcifs.internal.smb1.trans2.Trans2FindFirst2;
import jcifs.internal.smb1.trans2.Trans2FindFirst2Response;
import jcifs.internal.smb2.ServerMessageBlock2;
import jcifs.internal.smb2.tree.Smb2TreeConnectRequest;
import jcifs.internal.smb2.tree.Smb2TreeDisconnectRequest;
class SmbTreeImpl implements SmbTreeInternal {
private static final Logger log = LoggerFactory.getLogger(SmbTreeImpl.class);
private static AtomicLong TREE_CONN_COUNTER = new AtomicLong();
/*
* 0 - not connected
* 1 - connecting
* 2 - connected
* 3 - disconnecting
*/
private final AtomicInteger connectionState = new AtomicInteger();
private final String share;
private final String service0;
private final SmbSessionImpl session;
private volatile int tid;
private volatile String service = "?????";
private volatile boolean inDfs, inDomainDfs;
private volatile long treeNum; // used by SmbFile.isOpen
private final AtomicLong usageCount = new AtomicLong(0);
private final AtomicBoolean sessionAcquired = new AtomicBoolean(true);
private final boolean traceResource;
private final List acquires;
private final List releases;
private DfsReferralData treeReferral;
SmbTreeImpl ( SmbSessionImpl session, String share, String service ) {
this.session = session.acquire();
this.share = share.toUpperCase();
if ( service != null && !service.startsWith("??") ) {
this.service = service;
}
this.service0 = this.service;
this.traceResource = this.session.getConfig().isTraceResourceUsage();
if ( this.traceResource ) {
this.acquires = new LinkedList<>();
this.releases = new LinkedList<>();
}
else {
this.acquires = null;
this.releases = null;
}
}
boolean matches ( String shr, String servc ) {
return this.share.equalsIgnoreCase(shr) && ( servc == null || servc.startsWith("??") || this.service.equalsIgnoreCase(servc) );
}
@Override
public boolean equals ( Object obj ) {
if ( obj instanceof SmbTreeImpl ) {
SmbTreeImpl tree = (SmbTreeImpl) obj;
return matches(tree.share, tree.service);
}
return false;
}
public SmbTreeImpl acquire () {
return acquire(true);
}
/**
* {@inheritDoc}
*
* @see jcifs.SmbTree#unwrap(java.lang.Class)
*/
@SuppressWarnings ( "unchecked" )
@Override
public T unwrap ( Class type ) {
if ( type.isAssignableFrom(this.getClass()) ) {
return (T) this;
}
throw new ClassCastException();
}
/**
* @param track
* @return tree with increased usage count
*/
public SmbTreeImpl acquire ( boolean track ) {
long usage = this.usageCount.incrementAndGet();
if ( log.isTraceEnabled() ) {
log.trace("Acquire tree " + usage + " " + this);
}
if ( track && this.traceResource ) {
synchronized ( this.acquires ) {
this.acquires.add(truncateTrace(Thread.currentThread().getStackTrace()));
}
}
if ( usage == 1 ) {
synchronized ( this ) {
if ( this.sessionAcquired.compareAndSet(false, true) ) {
log.debug("Reacquire session");
this.session.acquire();
}
}
}
return this;
}
/**
* {@inheritDoc}
*
* @see java.lang.AutoCloseable#close()
*/
@Override
public void close () {
release(false);
}
public void release () {
release(true);
}
/**
* @param track
*/
public void release ( boolean track ) {
long usage = this.usageCount.decrementAndGet();
if ( log.isTraceEnabled() ) {
log.trace("Release tree " + usage + " " + this);
}
if ( track && this.traceResource ) {
synchronized ( this.releases ) {
this.releases.add(truncateTrace(Thread.currentThread().getStackTrace()));
}
}
if ( usage == 0 ) {
synchronized ( this ) {
log.debug("Usage dropped to zero, release session");
if ( this.sessionAcquired.compareAndSet(true, false) ) {
this.session.release();
}
}
}
else if ( usage < 0 ) {
log.error("Usage count dropped below zero " + this);
dumpResource();
throw new RuntimeCIFSException("Usage count dropped below zero");
}
}
/**
* @param stackTrace
* @return
*/
private static StackTraceElement[] truncateTrace ( StackTraceElement[] stackTrace ) {
int s = 2;
int e = stackTrace.length;
for ( int i = s; i < e; i++ ) {
StackTraceElement se = stackTrace[ i ];
if ( i == s && SmbTreeImpl.class.getName().equals(se.getClassName()) && "close".equals(se.getMethodName()) ) {
s++;
continue;
}
if ( se.getClassName().startsWith("org.junit.runners.") ) {
e = i - 4;
break;
}
}
StackTraceElement[] res = new StackTraceElement[e - s];
System.arraycopy(stackTrace, s, res, 0, e - s);
return res;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize () throws Throwable {
if ( isConnected() && this.usageCount.get() != 0 ) {
log.warn("Tree was not properly released");
}
}
/**
*
* @return whether the tree is connected
*/
public boolean isConnected () {
return this.tid != 0 && this.session.isConnected() && this.connectionState.get() == 2;
}
/**
* @return the type of this tree
*/
public int getTreeType () {
String connectedService = getService();
if ( "LPT1:".equals(connectedService) ) {
return SmbConstants.TYPE_PRINTER;
}
else if ( "COMM".equals(connectedService) ) {
return SmbConstants.TYPE_COMM;
}
return SmbConstants.TYPE_SHARE;
}
/**
* @return the service
*/
public String getService () {
return this.service;
}
/**
* @return the share
*/
public String getShare () {
return this.share;
}
/**
* @return whether this is a DFS share
*/
public boolean isDfs () {
return this.inDfs;
}
/**
*
*/
void markDomainDfs () {
this.inDomainDfs = true;
}
/**
* @return whether this tree was accessed using domain DFS
*/
public boolean isInDomainDfs () {
return this.inDomainDfs;
}
/**
* @param referral
*/
public void setTreeReferral ( DfsReferralData referral ) {
this.treeReferral = referral;
}
/**
* @return the treeReferral
*/
public DfsReferralData getTreeReferral () {
return this.treeReferral;
}
/**
* @return whether this tree may be a DFS share
* @throws SmbException
*/
public boolean isPossiblyDfs () throws SmbException {
if ( this.connectionState.get() == 2 ) {
// we are connected, so we know
return isDfs();
}
try ( SmbTransportImpl transport = this.session.getTransport() ) {
return transport.getNegotiateResponse().isDFSSupported();
}
}
/**
* @return the session this tree is connected in
*/
public SmbSessionImpl getSession () {
return this.session.acquire();
}
/**
* @return the tid
*/
public int getTid () {
return this.tid;
}
/**
* @return the tree_num (monotoincally increasing counter to track reconnects)
*/
public long getTreeNum () {
return this.treeNum;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode () {
return this.share.hashCode() + 7 * this.service.hashCode();
}
@Override
public T send ( jcifs.internal.Request request, RequestParam... params ) throws CIFSException {
return send(
(CommonServerMessageBlockRequest) request,
null,
( params != null && params.length > 0 ) ? EnumSet.copyOf(Arrays.asList(params)) : EnumSet.noneOf(RequestParam.class));
}
T send ( CommonServerMessageBlockRequest request, T response ) throws CIFSException {
return send(request, response, Collections. emptySet());
}
T send ( CommonServerMessageBlockRequest request, T response, Set params )
throws CIFSException {
try ( SmbSessionImpl sess = getSession();
SmbTransportImpl transport = sess.getTransport() ) {
if ( response != null ) {
response.clearReceived();
}
// try TreeConnectAndX with the request
// this does not make any sense if we are disconnecting right now
T chainedResponse = null;
if ( ! ( request instanceof SmbComTreeDisconnect ) && ! ( request instanceof Smb2TreeDisconnectRequest ) ) {
chainedResponse = treeConnect(request, response);
}
if ( request == null || ( chainedResponse != null && chainedResponse.isReceived() ) ) {
return chainedResponse;
}
// fall trough if the tree connection is already established
// and send it as a separate request instread
String svc = null;
int t = this.tid;
if ( t == 0 ) {
throw new SmbException("Tree id is 0");
}
request.setTid(t);
if ( !transport.isSMB2() ) {
ServerMessageBlock req = (ServerMessageBlock) request;
svc = this.service;
if ( svc == null ) {
// there still is some kind of race condition, where?
// this used to trigger "invalid operation..."
throw new SmbException("Service is null in state " + this.connectionState.get());
}
checkRequest(transport, req, svc);
}
if ( this.inDfs && !"IPC".equals(svc) && request instanceof RequestWithPath ) {
/*
* When DFS is in action all request paths are
* full UNC paths minus the first backslash like
* \server\share\path\to\file
* as opposed to normally
* \path\to\file
*/
RequestWithPath preq = (RequestWithPath) request;
if ( preq.getPath() != null && preq.getPath().length() > 0 ) {
preq.setResolveInDfs(true);
preq.setPath(preq.getFullUNCPath());
}
}
try {
return sess.send(request, response, params);
}
catch ( SmbException se ) {
if ( se.getNtStatus() == NtStatus.NT_STATUS_NETWORK_NAME_DELETED ) {
/*
* Someone removed the share while we were
* connected. Bastards! Disconnect this tree
* so that it reconnects cleanly should the share
* reappear in this client's lifetime.
*/
log.debug("Disconnect tree on NT_STATUS_NETWORK_NAME_DELETED");
treeDisconnect(true, true);
}
throw se;
}
}
}
/**
* @param transport
* @param request
* @throws SmbException
*/
private static void checkRequest ( SmbTransportImpl transport, ServerMessageBlock request, String svc ) throws SmbException {
if ( !"A:".equals(svc) ) {
switch ( request.getCommand() ) {
case ServerMessageBlock.SMB_COM_OPEN_ANDX:
case ServerMessageBlock.SMB_COM_NT_CREATE_ANDX:
case ServerMessageBlock.SMB_COM_READ_ANDX:
case ServerMessageBlock.SMB_COM_WRITE_ANDX:
case ServerMessageBlock.SMB_COM_CLOSE:
case ServerMessageBlock.SMB_COM_TREE_DISCONNECT:
break;
case ServerMessageBlock.SMB_COM_TRANSACTION:
case ServerMessageBlock.SMB_COM_TRANSACTION2:
switch ( ( (SmbComTransaction) request ).getSubCommand() & 0xFF ) {
case SmbComTransaction.NET_SHARE_ENUM:
case SmbComTransaction.NET_SERVER_ENUM2:
case SmbComTransaction.NET_SERVER_ENUM3:
case SmbComTransaction.TRANS_PEEK_NAMED_PIPE:
case SmbComTransaction.TRANS_WAIT_NAMED_PIPE:
case SmbComTransaction.TRANS_CALL_NAMED_PIPE:
case SmbComTransaction.TRANS_TRANSACT_NAMED_PIPE:
case SmbComTransaction.TRANS2_GET_DFS_REFERRAL:
break;
default:
throw new SmbException("Invalid operation for " + svc + " service: " + request);
}
break;
default:
throw new SmbException("Invalid operation for " + svc + " service" + request);
}
}
}
@SuppressWarnings ( "unchecked" )
T treeConnect ( CommonServerMessageBlockRequest andx, T andxResponse ) throws CIFSException {
CommonServerMessageBlockRequest request = null;
TreeConnectResponse response = null;
try ( SmbSessionImpl sess = getSession();
SmbTransportImpl transport = sess.getTransport() ) {
synchronized ( transport ) {
// this needs to be done before the reference to the remote hostname later
transport.ensureConnected();
if ( waitForState(transport) == 2 ) {
// already connected
return null;
}
int before = this.connectionState.getAndSet(1);
if ( before == 1 ) {
// concurrent connection attempt
if ( waitForState(transport) == 2 ) {
// finished connecting
return null;
}
// failure to connect
throw new SmbException("Tree disconnected while waiting for connection");
}
else if ( before == 2 ) {
// concurrently connected
return null;
}
try {
/*
* The hostname to use in the path is only known for
* sure if the NetBIOS session has been successfully
* established.
*/
String tconHostName = sess.getTargetHost();
if ( tconHostName == null ) {
throw new SmbException("Transport disconnected while waiting for connection");
}
SmbNegotiationResponse nego = transport.getNegotiateResponse();
String unc = "\\\\" + tconHostName + '\\' + this.share;
/*
* IBM iSeries doesn't like specifying a service. Always reset
* the service to whatever was determined in the constructor.
*/
String svc = this.service0;
/*
* Tree Connect And X Request / Response
*/
if ( log.isDebugEnabled() ) {
log.debug("treeConnect: unc=" + unc + ",service=" + svc);
}
if ( transport.isSMB2() ) {
Smb2TreeConnectRequest req = new Smb2TreeConnectRequest(sess.getConfig(), unc);
if ( andx != null ) {
req.chain((ServerMessageBlock2) andx);
}
request = req;
}
else {
response = new SmbComTreeConnectAndXResponse(sess.getConfig(), (ServerMessageBlock) andxResponse);
request = new SmbComTreeConnectAndX(
sess.getContext(),
( (SmbComNegotiateResponse) nego ).getServerData(),
unc,
svc,
(ServerMessageBlock) andx);
}
response = sess.send(request, response);
treeConnected(transport, response);
if ( andxResponse != null && andxResponse.isReceived() ) {
return andxResponse;
}
else if ( transport.isSMB2() ) {
return (T) response.getNextResponse();
}
return null;
}
catch ( IOException se ) {
if ( request != null && request.getResponse() != null ) {
// tree connect might still have succeeded
response = (TreeConnectResponse) request.getResponse();
if ( response.isReceived() && !response.isError() && response.getErrorCode() == NtStatus.NT_STATUS_OK ) {
if ( !transport.isDisconnected() ) {
treeConnected(transport, response);
}
throw se;
}
}
try {
log.debug("Disconnect tree on treeConnectFailure", se);
treeDisconnect(true, true);
}
finally {
this.connectionState.set(0);
}
throw se;
}
finally {
transport.notifyAll();
}
}
}
}
/**
* @param transport
* @param response
* @throws SmbException
*/
private void treeConnected ( SmbTransportImpl transport, TreeConnectResponse response ) throws SmbException {
int treeId = response.getTid();
if ( treeId == 0 ) {
throw new SmbException("TreeID is NULL");
}
this.tid = treeId;
String rsvc = response.getService();
if ( rsvc == null && !transport.isSMB2() ) {
throw new SmbException("Service is NULL");
}
this.service = rsvc;
this.inDfs = response.isShareDfs();
this.treeNum = TREE_CONN_COUNTER.incrementAndGet();
this.connectionState.set(2); // connected
}
/**
* @param transport
* @return
* @throws SmbException
*/
private int waitForState ( SmbTransportImpl transport ) throws SmbException {
int cs;
while ( ( cs = this.connectionState.get() ) != 0 ) {
if ( cs == 2 ) {
return cs;
}
if ( cs == 3 ) {
throw new SmbException("Disconnecting during tree connect");
}
try {
log.debug("Waiting for transport");
transport.wait();
}
catch ( InterruptedException ie ) {
throw new SmbException(ie.getMessage(), ie);
}
}
return cs;
}
/**
*
* {@inheritDoc}
*
* @see jcifs.smb.SmbTreeInternal#connectLogon(jcifs.CIFSContext)
*/
@Override
public void connectLogon ( CIFSContext tf ) throws SmbException {
if ( tf.getConfig().getLogonShare() == null ) {
try {
treeConnect(null, null);
}
catch ( CIFSException e ) {
SmbException.wrap(e);
}
}
else {
Trans2FindFirst2 req = new Trans2FindFirst2(
tf.getConfig(),
"\\",
"*",
SmbConstants.ATTR_DIRECTORY,
tf.getConfig().getListCount(),
tf.getConfig().getListSize());
Trans2FindFirst2Response resp = new Trans2FindFirst2Response(tf.getConfig());
try {
send(req, resp);
}
catch ( SmbException e ) {
throw e;
}
catch ( CIFSException e ) {
throw new SmbException("Logon share connection failed", e);
}
}
}
boolean treeDisconnect ( boolean inError, boolean inUse ) {
boolean wasInUse = false;
try ( SmbSessionImpl sess = getSession();
SmbTransportImpl transport = sess.getTransport() ) {
synchronized ( transport ) {
int st = this.connectionState.getAndSet(3);
if ( st == 2 ) {
long l = this.usageCount.get();
if ( ( inUse && l != 1 ) || ( !inUse && l > 0 ) ) {
log.warn("Disconnected tree while still in use " + this);
dumpResource();
wasInUse = true;
if ( sess.getConfig().isTraceResourceUsage() ) {
throw new RuntimeCIFSException("Disconnected tree while still in use");
}
}
if ( !inError && this.tid != 0 ) {
try {
if ( transport.isSMB2() ) {
Smb2TreeDisconnectRequest req = new Smb2TreeDisconnectRequest(sess.getConfig());
send(req.ignoreDisconnect());
}
else {
send(new SmbComTreeDisconnect(sess.getConfig()), new SmbComBlankResponse(sess.getConfig()));
}
}
catch ( CIFSException se ) {
log.error("Tree disconnect failed", se);
}
}
}
this.inDfs = false;
this.inDomainDfs = false;
this.connectionState.set(0);
transport.notifyAll();
}
}
return wasInUse;
}
/**
*
*/
private void dumpResource () {
if ( !this.traceResource ) {
return;
}
synchronized ( this.acquires ) {
for ( StackTraceElement[] acq : this.acquires ) {
log.debug("Acquire " + Arrays.toString(acq));
}
}
synchronized ( this.releases ) {
for ( StackTraceElement[] rel : this.releases ) {
log.debug("Release " + Arrays.toString(rel));
}
}
}
@Override
public String toString () {
return "SmbTree[share=" + this.share + ",service=" + this.service + ",tid=" + this.tid + ",inDfs=" + this.inDfs + ",inDomainDfs="
+ this.inDomainDfs + ",connectionState=" + this.connectionState + ",usage=" + this.usageCount.get() + "]";
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy