|
|
/*
* F S P U T . C P P * * Copyright 1986-1998 Microsoft Corporation, All Rights Reserved */
#include "_davfs.h"
// ========================================================================
//
// CLASS CPutRequest
//
// Encapsulates the entire PUT request as an object which can be
// can be reentered at various points for asynchronous processing.
//
class CPutRequest : public CMTRefCounted, private IAsyncStream, private IAsyncPersistObserver, private CDavWorkContext { //
// Reference to the CMethUtil
//
auto_ref_ptr<CMethUtil> m_pmu;
//
// Cached request URI
//
LPCWSTR m_pwszURI;
//
// Cached translated URI
//
LPCWSTR m_pwszPath;
//
// File handle of target.
//
auto_ref_handle m_hf;
//
// Boolean flag indicating whether the file is being created
// as a result of this PUT. Used to tell whether we need
// to delete the file on failure as well as determine
// whether to send back a 200 OK or a 201 Created response.
//
BOOL m_fCreatedFile;
//
// OVERLAPPED structure with file pointer info necessary
// for async file I/O
//
OVERLAPPED m_ov;
//
// Minimum number of milliseconds between polls for WriteFile()
// I/O completion. This number increases geometrically by a factor
// (below) to minimize polling by worker threads.
//
DWORD m_dwMsecPollDelay;
//
// Initial poll delay (in milliseconds) and factor by which
// that delay is increased each we poll and find that the
// I/O has not completed. The factor is expressed as a
// fraction: POLL_DELAY_NUMERATOR/POLL_DELAY_DENOMINATOR.
// Note that the new value is computed using integer arithmetic
// so choose values such that the delay will actually increase!
//
//$OPT Are these values optimal? Ideally, we want the
//$OPT first poll to happen immediately after the I/O
//$OPT completes.
//
enum { MSEC_POLL_DELAY_INITIAL = 10, POLL_DELAY_NUMERATOR = 2, POLL_DELAY_DENOMINATOR = 1 };
//
// Number of bytes written in the last write operation.
//
DWORD m_dwcbWritten;
//
// Observer passed to AsyncWrite() to notify when the
// write completes.
//
IAsyncWriteObserver * m_pobs;
//
// Status
//
SCODE m_sc;
// MANIPULATORS
//
VOID SendResponse();
VOID AsyncWrite( const BYTE * pbBuf, UINT cbToWrite, IAsyncWriteObserver& obsAsyncWrite );
VOID PostIOCompletionPoll();
// CDavWorkContext callback called to poll for I/O completion
//
DWORD DwDoWork();
VOID WriteComplete();
VOID PersistComplete( HRESULT hr );
// NOT IMPLEMENTED
//
CPutRequest& operator=( const CPutRequest& ); CPutRequest( const CPutRequest& );
public: // CREATORS
//
CPutRequest( CMethUtil * pmu ) : m_pmu(pmu), m_pwszURI(pmu->LpwszRequestUrl()), m_pwszPath(pmu->LpwszPathTranslated()), m_fCreatedFile(FALSE), m_pobs(NULL), m_sc(S_OK) { m_ov.hEvent = NULL; m_ov.Offset = 0; m_ov.OffsetHigh = 0; }
~CPutRequest() { if ( m_ov.hEvent ) CloseHandle( m_ov.hEvent ); }
// MANIPULATORS
//
void AddRef() { CMTRefCounted::AddRef(); } void Release() { CMTRefCounted::Release(); } VOID Execute(); };
// ------------------------------------------------------------------------
//
// CPutRequest::Execute()
//
// Process the request up to the point where we persist the body.
//
VOID CPutRequest::Execute() { LPCWSTR pwsz;
PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::Execute() called\n", GetCurrentThreadId(), this );
//
// First off, tell the pmu that we want to defer the response.
// Even if we send it synchronously (i.e. due to an error in
// this function), we still want to use the same mechanism that
// we would use for async.
//
m_pmu->DeferResponse();
// Do ISAPI application and IIS access bits checking
//
m_sc = m_pmu->ScIISCheck (m_pwszURI, MD_ACCESS_WRITE); if (FAILED(m_sc)) { // Either the request has been forwarded, or some bad error occurred.
// In either case, quit here and map the error!
//
DebugTrace( "Dav: ScIISCheck() failed (0x%08lX)\n", m_sc ); SendResponse(); return; }
// From the HTTP/1.1 draft (update to RFC2068), Section 9.6:
// The recipient of the entity MUST NOT ignore any Content-*
// (e.g. Content-Range) headers that it does not understand
// or implement and MUST return a 501 (Not Implemented) response
// in such cases.
// So, let's do the checking....
//
if (m_pmu->LpwszGetRequestHeader(gc_szContent_Range, FALSE)) { m_sc = E_DAV_NO_PARTIAL_UPDATE; // 501 Not Implemented
SendResponse(); return; }
// For PUT, content-length is required
//
if (NULL == m_pmu->LpwszGetRequestHeader (gc_szContent_Length, FALSE)) { pwsz = m_pmu->LpwszGetRequestHeader (gc_szTransfer_Encoding, FALSE); if (!pwsz || _wcsicmp (pwsz, gc_wszChunked)) { DavTrace ("Dav: PUT: missing content-length in request\n"); m_sc = E_DAV_MISSING_LENGTH; SendResponse(); return; } }
// See if we are trying to trash the VROOT
//
if (m_pmu->FIsVRoot (m_pwszURI)) { m_sc = E_DAV_PROTECTED_ENTITY; SendResponse(); return; }
// This method is gated by If-xxx headers
//
m_sc = ScCheckIfHeaders (m_pmu.get(), m_pwszPath, FALSE); if (m_sc != S_OK) { DebugTrace ("Dav: If-xxx failed their check\n"); SendResponse(); return; }
// Check state headers.
//
m_sc = HrCheckStateHeaders (m_pmu.get(), m_pwszPath, FALSE); if (FAILED (m_sc)) { DebugTrace ("DavFS: If-State checking failed.\n"); SendResponse(); return; }
// If we have a locktoken, try to get the lock handle from the cache.
// If this fails, fall through and do the normal processing.
// DO NOT put LOCK handles into an auto-object!! The CACHE still owns it!!!
//
pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE); if (!pwsz || !FGetLockHandle (m_pmu.get(), m_pwszPath, GENERIC_WRITE, pwsz, &m_hf)) { // Open the file manually.
//
if (!m_hf.FCreate( DavCreateFile (m_pwszPath, // filename
GENERIC_WRITE, // dwAccess
0, //FILE_SHARE_READ | FILE_SHARE_WRITE, // DO conflict with OTHER write locks
NULL, // lpSecurityAttributes
OPEN_ALWAYS, // creation flags
FILE_ATTRIBUTE_NORMAL | // attributes
FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, NULL))) // template
{ DWORD dwErr = GetLastError();
// Return 409 Conflict in the case we were told that
// path was not found, that will indicate that the parent
// does not exist
//
if (ERROR_PATH_NOT_FOUND == dwErr) { m_sc = E_DAV_NONEXISTING_PARENT; } else { m_sc = HRESULT_FROM_WIN32(dwErr); }
// Special work for 416 Locked responses -- fetch the
// comment & set that as the response body.
//
if (FLockViolation (m_pmu.get(), dwErr, m_pwszPath, GENERIC_WRITE)) { m_sc = E_DAV_LOCKED; } else { DWORD dwFileAttr;
//$Raid
// Special processing to find out if the resource is an
// existing directory
//
if (static_cast<DWORD>(-1) != (dwFileAttr = GetFileAttributesW (m_pwszPath))) { if (dwFileAttr & FILE_ATTRIBUTE_DIRECTORY) m_sc = E_DAV_COLLECTION_EXISTS; } }
DebugTrace ("Dav: failed to open the file for writing\n"); SendResponse(); return; }
// Change the default error code to indicate the
// creation of the file or the existance of the file.
//
if (GetLastError() != ERROR_ALREADY_EXISTS) { // Emit the location
//
m_pmu->EmitLocation (gc_szLocation, m_pwszURI, FALSE); m_fCreatedFile = TRUE; } // Make sure the content-location reflects the corrected URI
//
else if (FTrailingSlash (m_pwszURI)) m_pmu->EmitLocation (gc_szContent_Location, m_pwszURI, FALSE); }
//
// Add a ref for the async persist and sloughf the data across.
// Note that we use an auto_ref_ptr rather than AddRef() directly
// because the persist call throws on failure and we need to clean
// up the reference if it does.
//
{ auto_ref_ptr<CPutRequest> pRef(this);
PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::Execute() calling AsyncPersistRequestBody()\n", GetCurrentThreadId(), this );
m_pmu->AsyncPersistRequestBody( *this, *this );
pRef.relinquish(); } }
// ------------------------------------------------------------------------
//
// CPutRequest::AsyncWrite()
//
// Called indirectly from AsyncPersistRequestBody() periodically to
// write bytes to the file.
//
void CPutRequest::AsyncWrite( const BYTE * pbBuf, UINT cbToWrite, IAsyncWriteObserver& obs ) { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::AsyncWrite() writing %d bytes\n", GetCurrentThreadId(), this, cbToWrite );
//
// Stash the async write observer passed to us so that
// we can call it when the write completes
//
m_pobs = &obs;
//
// Start writing. I/O may complete before WriteFile() returns
// in which case we continue to execute synchronously. If I/O
// is pending when WriteFile() returns, we complete the I/O
// asynchronously.
//
if ( WriteFile( m_hf.get(), pbBuf, cbToWrite, &m_dwcbWritten, &m_ov ) ) { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::AsyncWrite(): WriteFile() succeeded\n", GetCurrentThreadId(), this );
//
// WriteFile() executed synchronously, so call
// the completion routine now and keep processing
// on the current thread.
//
WriteComplete(); } else if ( ERROR_IO_PENDING == GetLastError() ) { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::AsyncWrite(): WriteFile() executing asynchronously...\n", GetCurrentThreadId(), this );
//
// Set the polling delay to its initial value. The polling delay
// is the amount of time before we'll check for I/O completion.
// As described in the CPutRequest class definition, this delay
// grows geometrically each time that the I/O is still pending
// when polled. The value is only a hint -- polling may actually
// execute before or after, depending on server load.
//
m_dwMsecPollDelay = MSEC_POLL_DELAY_INITIAL;
//
// WriteFile() is executing asynchronously, so make sure we
// find out when it completes.
//
PostIOCompletionPoll(); } else { DebugTrace( "CPutRequest::AsyncWrite() - WriteFile() failed (%d)\n", GetLastError() ); m_sc = HRESULT_FROM_WIN32(GetLastError()); WriteComplete(); } }
// ------------------------------------------------------------------------
//
// CPutRequest::PostIOCompletionPoll()
//
// Post a work context to poll for WriteFile() I/O completion.
//
VOID CPutRequest::PostIOCompletionPoll() { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::PostIOCompletionPoll() called\n", GetCurrentThreadId(), this );
//
// Post ourselves as a work context that will periodically
// poll for async I/O completion. If successful our DwDoWork()
// (inherited from CDAVWorkContext) will eventually be called
// at some time > m_dwMsecPollDelay to poll for completion.
//
{ auto_ref_ptr<CPutRequest> pRef(this);
if ( CPoolManager::PostDelayedWork(this, m_dwMsecPollDelay) ) { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::PostIOCompletionPoll(): PostDelayedWork() succeeded\n", GetCurrentThreadId(), this ); pRef.relinquish(); return; } }
//
// If we were unable to post the work context for any reason
// we must wait for I/O completion and then call the completion
// routine manually.
//
DebugTrace( "CPutRequest::PostIOCompletionPoll() - CPoolManager::PostDelayedWork() failed (%d). Waiting for completion....\n", GetLastError() ); if ( GetOverlappedResult( m_hf.get(), &m_ov, &m_dwcbWritten, TRUE ) ) { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::PostIOCompletionPoll(): GetOverlappedResult() succeeded\n", GetCurrentThreadId(), this ); WriteComplete(); return; }
DebugTrace( "CPutRequest::PostIOCompletionPoll() - GetOverlappedResult() failed (%d).\n", GetLastError() ); m_sc = HRESULT_FROM_WIN32(GetLastError()); SendResponse(); }
// ------------------------------------------------------------------------
//
// CPutRequest::DwDoWork()
//
// Work completion callback routine. Called when the work context posted
// above executes.
//
DWORD CPutRequest::DwDoWork() { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::DwDoWork() called\n", GetCurrentThreadId(), this );
//
// Take ownership of the reference added for posting.
//
auto_ref_ptr<CPutRequest> pRef; pRef.take_ownership(this);
//
// Quickly check whether the I/O has completed. If it has,
// then call the completion routine. If not, then repost
// the polling context with a geometrically longer delay.
//
if ( HasOverlappedIoCompleted(&m_ov) ) { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::DwDoWork(): Overlapped I/O complete\n", GetCurrentThreadId(), this );
if ( !GetOverlappedResult( m_hf.get(), &m_ov, &m_dwcbWritten, FALSE ) ) { DebugTrace( "CPutRequest::DwDoWork() - Error in overlapped I/O (%d)\n", GetLastError() ); m_sc = HRESULT_FROM_WIN32(GetLastError()); }
WriteComplete(); } else { m_dwMsecPollDelay = (m_dwMsecPollDelay * POLL_DELAY_NUMERATOR) / POLL_DELAY_DENOMINATOR;
PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::DwDoWork(): I/O still pending. Increasing delay to %lu msec.\n", GetCurrentThreadId(), this, m_dwMsecPollDelay );
PostIOCompletionPoll(); }
return 0; }
// ------------------------------------------------------------------------
//
// CPutRequest::WriteComplete()
//
// Called when WriteFile() I/O completes -- either synchronously or
// asynchronously, with or without an error.
//
void CPutRequest::WriteComplete() { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::WriteComplete() called. %d bytes written\n", GetCurrentThreadId(), this, m_dwcbWritten );
//
// If there was an error, then gripe about it.
//
if ( FAILED(m_sc) ) DebugTrace( "CPutRequest::WriteComplete() - Write() error (as an HRESULT) 0x%08lX\n", m_sc );
//
// Update the current file position
//
m_ov.Offset += m_dwcbWritten;
//
// Resume processing by notifying the observer
// we registered in AsyncWrite();
//
Assert( m_pobs ); m_pobs->WriteComplete( m_dwcbWritten, m_sc ); }
// ------------------------------------------------------------------------
//
// CPutRequest::PersistComplete()
//
// AsyncPersistRequestBody() callback called when persisting completes.
//
VOID CPutRequest::PersistComplete( HRESULT hr ) { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::PersistComplete() called\n", GetCurrentThreadId(), this );
//
// Take ownership of the reference added for the async persist.
//
auto_ref_ptr<CPutRequest> pRef; pRef.take_ownership(this);
//
// If the copy operation failed, gripe and send back
// an appropriate response.
//
m_sc = hr; if ( FAILED(m_sc ) ) { DebugTrace( "CPutRequest::PersistComplete() - Error persiting request body (0x%08lX)\n", m_sc ); SendResponse(); return; }
//
// Set EOF
//
SetFilePointer (m_hf.get(), m_ov.Offset, NULL, FILE_BEGIN);
SetEndOfFile (m_hf.get());
// Set the Content-xxx properties
//
m_sc = ScSetContentProperties (m_pmu.get(), m_pwszPath, m_hf.get()); if ( FAILED(m_sc) ) { DebugTrace( "CPutRequest::PersistComplete() - ScSetContentProperties() failed (0x%08lX)\n", m_sc ); SendResponse(); return; }
// Passback an allow header
//
m_pmu->SetAllowHeader (RT_DOCUMENT);
// Send the response
//
SendResponse(); }
// ------------------------------------------------------------------------
//
// CPutRequest::SendResponse()
//
// Set the response code and send the response.
//
VOID CPutRequest::SendResponse() { PutTrace( "DAV: TID %3d: 0x%08lX: CPutRequest::SendResponse() called\n", GetCurrentThreadId(), this );
// Clear out any old handle here. We can't send any response
// back while we are still holding a handle, otherwise, there
// exists the chance that a client request comes immediately to
// access this resource and receive 423 locked.
//
m_hf.clear();
//
// Set the response code and go
//
if ( SUCCEEDED(m_sc) ) { if ( m_fCreatedFile ) m_sc = W_DAV_CREATED; } else { if ( m_fCreatedFile ) { // WARNING: the safe_revert class should only be
// used in very selective situations. It is not
// a "quick way to get around" impersonation.
//
safe_revert sr(m_pmu->HitUser());
DavDeleteFile (m_pwszPath); DebugTrace ("Dav: deleting partial put (%ld)\n", GetLastError()); } }
m_pmu->SetResponseCode (HscFromHresult(m_sc), NULL, 0, CSEFromHresult(m_sc));
m_pmu->SendCompleteResponse(); }
/*
* DAVPut() * * Purpose: * * Win32 file system implementation of the DAV PUT method. The * PUT method creates a file in the DAV name space and populates * the file with the data found in the passed in request. The * response created indicates the success of the call. * * Parameters: * * pmu [in] pointer to the method utility object */ void DAVPut (LPMETHUTIL pmu) { auto_ref_ptr<CPutRequest> pRequest(new CPutRequest(pmu));
PutTrace( "DAV: CPutRequest: TID %3d: 0x%08lX Calling CPutRequest::Execute() \n", GetCurrentThreadId(), pRequest );
pRequest->Execute(); }
|