windows-nt/Source/XPSP1/NT/net/sfm/afp/server/nwtrash.c

788 lines
18 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*
Copyright (c) 1992 Microsoft Corporation
Module Name:
nwtrash.c
Abstract:
This module contains the routines for performing Network Trash Folder
operations.
Author:
Sue Adams (microsoft!suea)
Revision History:
06 Aug 1992 Initial Version
Notes: Tab stop: 4
--*/
#define NWTRASH_LOCALS
#define FILENUM FILE_NWTRASH
#include <afp.h>
#include <fdparm.h>
#include <pathmap.h>
#include <nwtrash.h>
#include <afpinfo.h>
#include <access.h>
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, AfpCreateNetworkTrash)
#pragma alloc_text( PAGE, AfpDeleteNetworkTrash)
#pragma alloc_text( PAGE, afpCleanNetworkTrash)
#pragma alloc_text( PAGE, AfpWalkDirectoryTree)
#pragma alloc_text( PAGE, afpPushDirNode)
#pragma alloc_text( PAGE, afpPopDirNode)
#pragma alloc_text( PAGE, AfpGetNextDirectoryInfo)
#pragma alloc_text( PAGE, afpNwtDeleteFileEntity)
#endif
/*** AfpCreateNetworkTrash
*
* Create the network trash folder for a newly added volume.
* Make sure it is hidden and make sure the streams are intact.
* This routine may only be called for NTFS volumes. Note that even
* ReadOnly NTFS volumes will have a trash created. This is because
* if someone is going to toggle the volume ReadOnly bit, we don't need
* to worry about creating/deleting the trash on the fly.
* We keep an open handle to the network trash stored in the volume
* descriptor so that nobody can come in behind our backs and delete
* it.
*/
NTSTATUS
AfpCreateNetworkTrash(
IN PVOLDESC pVolDesc
)
{
FILESYSHANDLE hNWT;
PDFENTRY pDfEntry;
NTSTATUS Status;
ULONG info, Attr;
AFPINFO afpInfo;
BOOLEAN ReleaseSwmr = False;
PISECURITY_DESCRIPTOR pSecDesc;
FILEDIRPARM fdparm; // This is used to set the hidden attribute
// of the FinderInfo for network trash folder
PAGED_CODE( );
DBGPRINT(DBG_COMP_IDINDEX,DBG_LEVEL_INFO,("\tCreating Network Trash...\n"));
ASSERT(pVolDesc->vds_Flags & VOLUME_NTFS);
hNWT.fsh_FileHandle = NULL;
Status = AfpMakeSecurityDescriptorForUser(&AfpSidWorld, &AfpSidWorld, &pSecDesc);
if (!NT_SUCCESS(Status))
return Status;
ASSERT (pSecDesc != NULL);
ASSERT (pSecDesc->Dacl != NULL);
do
{
// NOTE: NTFS allows me to open a Readonly directory for
// delete access.
Status = AfpIoCreate(&pVolDesc->vds_hRootDir,
AFP_STREAM_DATA,
&AfpNetworkTrashNameU,
AFP_NWT_ACCESS | AFP_OWNER_ACCESS,
AFP_NWT_SHAREMODE,
AFP_NWT_OPTIONS,
AFP_NWT_DISPOSITION,
AFP_NWT_ATTRIBS, // makes it hidden
False,
pSecDesc,
&hNWT,
&info,
NULL,
NULL,
NULL);
// Free the memory allocated for the security descriptor
AfpFreeMemory(pSecDesc->Dacl);
AfpFreeMemory(pSecDesc);
if (!NT_SUCCESS(Status))
break;
ASSERT(info == FILE_CREATED);
// Add the AfpInfo stream
Status = AfpSlapOnAfpInfoStream(NULL,
NULL,
&hNWT,
NULL,
AFP_ID_NETWORK_TRASH,
True,
NULL,
&afpInfo);
if (!NT_SUCCESS(Status))
break;
// it does not exist in the ID index database, add it
AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock);
ReleaseSwmr = True;
ASSERT(pVolDesc->vds_pDfeRoot != NULL);
pDfEntry = AfpAddDfEntry(pVolDesc,
pVolDesc->vds_pDfeRoot,
&AfpNetworkTrashNameU,
True,
AFP_ID_NETWORK_TRASH);
if (pDfEntry == NULL)
{
Status = STATUS_UNSUCCESSFUL;
break;
}
// NOTE: pDfEntry now points to the "Network Trash Folder"
// Get directory info to cache
Status = AfpIoQueryTimesnAttr(&hNWT,
&pDfEntry->dfe_CreateTime,
&pDfEntry->dfe_LastModTime,
&Attr);
ASSERT(NT_SUCCESS(Status));
ASSERT(Attr & FILE_ATTRIBUTE_HIDDEN);
pDfEntry->dfe_NtAttr = (USHORT)Attr & FILE_ATTRIBUTE_VALID_FLAGS;
pDfEntry->dfe_AfpAttr = afpInfo.afpi_Attributes;
pDfEntry->dfe_FinderInfo = afpInfo.afpi_FinderInfo;
pDfEntry->dfe_BackupTime = afpInfo.afpi_BackupTime;
DFE_OWNER_ACCESS(pDfEntry) = (DIR_ACCESS_SEARCH | DIR_ACCESS_READ);
DFE_GROUP_ACCESS(pDfEntry) = (DIR_ACCESS_SEARCH | DIR_ACCESS_READ);
DFE_WORLD_ACCESS(pDfEntry) = (DIR_ACCESS_SEARCH | DIR_ACCESS_READ);
// ok, we know it now exists both on disk and in the database
if (NT_SUCCESS(Status))
{
RtlZeroMemory(&fdparm, sizeof(fdparm));
fdparm._fdp_Flags = DFE_FLAGS_DIR;
fdparm._fdp_AfpId = AFP_ID_NETWORK_TRASH;
fdparm._fdp_FinderInfo = afpInfo.afpi_FinderInfo;
// We must set the invisible flag in the finder info, because
// System 6 seems to ignore the hidden attribute.
pDfEntry->dfe_FinderInfo.fd_Attr1 |= FINDER_FLAG_INVISIBLE;
fdparm._fdp_FinderInfo.fd_Attr1 |= FINDER_FLAG_INVISIBLE;
Status = AfpSetAfpInfo(&hNWT,
FD_BITMAP_FINDERINFO,
&fdparm,
NULL,
NULL);
}
} while (False);
if (hNWT.fsh_FileHandle != NULL)
AfpIoClose(&hNWT);
if (!NT_SUCCESS(Status))
{
AFPLOG_ERROR(AFPSRVMSG_CREATE_NWTRASH,
Status,
NULL,
0,
&pVolDesc->vds_Name);
}
else
{
// Open a Network Trash handle to keep around so that no one can
// come in and delete the Network Trash dir out from under us
Status = AfpIoOpen(&pVolDesc->vds_hRootDir,
AFP_STREAM_DATA,
AFP_NWT_OPTIONS,
&AfpNetworkTrashNameU,
FILEIO_ACCESS_READ,
AFP_NWT_SHAREMODE,
False,
&pVolDesc->vds_hNWT);
if (!NT_SUCCESS(Status))
{
AFPLOG_ERROR(AFPSRVMSG_CREATE_NWTRASH, Status, NULL, 0,
&pVolDesc->vds_Name);
}
}
if (ReleaseSwmr)
{
AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);
}
return Status;
}
/*** AfpDeleteNetworkTrash
*
* Delete the network trash folder from disk when a volume is being added,
* deleted or stopped.
*
* NOTE: this must be called in the server's context to ensure that we have
* LOCAL_SYSTEM access to all the trash directories created by users
*/
NTSTATUS
AfpDeleteNetworkTrash(
IN PVOLDESC pVolDesc,
IN BOOLEAN VolumeStart // Is volume starting or is it stopping
)
{
FILESYSHANDLE hNWT;
NTSTATUS Status;
PAGED_CODE( );
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
("\tDeleting Network Trash...\n") );
ASSERT(pVolDesc->vds_Flags & VOLUME_NTFS);
if (!VolumeStart)
{
// Close the handle to Network Trash that we keep open so PC users can't
// delete the directory out from under us.
if (pVolDesc->vds_hNWT.fsh_FileHandle != NULL)
{
AfpIoClose(&pVolDesc->vds_hNWT);
pVolDesc->vds_hNWT.fsh_FileHandle = NULL;
}
}
do
{
AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock);
// Open for delete access
Status = AfpIoOpen(&pVolDesc->vds_hRootDir,
AFP_STREAM_DATA,
AFP_NWT_OPTIONS,
&AfpNetworkTrashNameU,
AFP_NWT_ACCESS,
AFP_NWT_SHAREMODE,
False,
&hNWT);
// there is no network trash folder to delete
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
{
Status = STATUS_SUCCESS;
break;
}
if (NT_SUCCESS(Status))
{
Status = afpCleanNetworkTrash(pVolDesc, &hNWT, NULL);
if (NT_SUCCESS(Status) || !VolumeStart)
{
// NOTE: NTFS will allow me to open the directory for
// DELETE access if it is marked Readonly, but I cannot delete it.
// Clear the Readonly Bit on the Network Trash Folder
AfpIoSetTimesnAttr(&hNWT,
NULL,
NULL,
0,
FILE_ATTRIBUTE_READONLY,
NULL,
NULL);
Status = AfpIoMarkFileForDelete(&hNWT,
NULL,
NULL,
NULL);
}
AfpIoClose(&hNWT);
}
} while (False);
AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);
if ((!NT_SUCCESS(Status)) && (Status != STATUS_OBJECT_NAME_NOT_FOUND))
{
AFPLOG_ERROR(AFPSRVMSG_DELETE_NWTRASH,
Status,
NULL,
0,
&pVolDesc->vds_Name);
Status = STATUS_UNSUCCESSFUL;
}
return Status;
}
/*** afpCleanNetworkTrash
*
* Delete the contents of the network trash folder referenced by hNWT.
* If pDfeNWT is non-null, then delete the file/dir entries from the IdIndex
* database. If pDfeNWT is null, the volume is being deleted and the
* IdIndex database is getting blown away too, so don't bother removing the
* entries.
*/
LOCAL
NTSTATUS
afpCleanNetworkTrash(
IN PVOLDESC pVolDesc,
IN PFILESYSHANDLE phNWT,
IN PDFENTRY pDfeNWT OPTIONAL
)
{
NTSTATUS Status;
PAGED_CODE( );
DBGPRINT(DBG_COMP_IDINDEX,DBG_LEVEL_INFO,("afpCleanNetworkTrash entered\n"));
if (pDfeNWT != NULL)
{
ASSERT(pDfeNWT->dfe_AfpId == AFP_ID_NETWORK_TRASH);
// clean out all child DFEntries belonging to the network trash
AfpPruneIdDb(pVolDesc,pDfeNWT);
}
Status = AfpWalkDirectoryTree(phNWT, afpNwtDeleteFileEntity);
return Status;
}
NTSTATUS
AfpWalkDirectoryTree(
IN PFILESYSHANDLE phTargetDir,
IN WALKDIR_WORKER NodeWorker
)
{
PFILE_DIRECTORY_INFORMATION tmpptr;
PWALKDIR_NODE DirNodeStacktop = NULL, pcurrentnode;
NTSTATUS rc, status = STATUS_SUCCESS;
PBYTE enumbuf;
PWCHAR nodename;
ULONG nodenamelen;
BOOLEAN isdir;
UNICODE_STRING udirname;
PAGED_CODE( );
//
// allocate the buffer that will hold enumerated files and dirs
//
if ((enumbuf = (PBYTE)AfpAllocPANonPagedMemory(AFP_ENUMBUF_SIZE)) == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
do // error handling loop
{
//
// prime the pump with the top level (target dir) directory handle
//
if ((rc = afpPushDirNode(&DirNodeStacktop, NULL, NULL))
!= STATUS_SUCCESS)
{
status = rc;
break;
}
else
{
DirNodeStacktop->wdn_Handle = *phTargetDir;
}
//
// keep popping enumerated directories off the stack until stack empty
//
while ((pcurrentnode = DirNodeStacktop) != NULL)
{
if (pcurrentnode->wdn_Enumerated == False)
{
//
// get a handle to the directory so it can be enumerated
//
if (pcurrentnode->wdn_Handle.fsh_FileHandle == NULL)
{
RtlInitUnicodeString(&udirname,
pcurrentnode->wdn_RelativePath.Buffer);
// open a handle to the thing relative to the phTargetDir
rc = AfpIoOpen(phTargetDir,
AFP_STREAM_DATA,
FILEIO_OPEN_DIR,
&udirname,
FILEIO_ACCESS_READ,
FILEIO_DENY_NONE,
False,
&pcurrentnode->wdn_Handle);
if (!NT_SUCCESS(rc))
{
status = rc;
break;
}
}
//
// keep enumerating till we get all the entries
//
while (True)
{
rc = AfpIoQueryDirectoryFile(&pcurrentnode->wdn_Handle,
(PFILE_DIRECTORY_INFORMATION)enumbuf,
AFP_ENUMBUF_SIZE,
FileDirectoryInformation,
False, // return multiple entries
False, // don't restart scan
NULL);
ASSERT(rc != STATUS_PENDING);
if ((rc == STATUS_NO_MORE_FILES) ||
(rc == STATUS_NO_SUCH_FILE))
{
pcurrentnode->wdn_Enumerated = True;
break; // that's it, we've seen everything there is
}
//
// NOTE: if we get STATUS_BUFFER_OVERFLOW, the IO status
// information field does NOT tell us the required size
// of the buffer, so we wouldn't know how big to realloc
// the enum buffer if we wanted to retry, so don't bother
else if (!NT_SUCCESS(rc))
{
status = rc;
break; // enumerate failed, bail out
}
//
// process the enumerated files and dirs
//
tmpptr = (PFILE_DIRECTORY_INFORMATION)enumbuf;
while (True)
{
rc = AfpGetNextDirectoryInfo(&tmpptr,
&nodename,
&nodenamelen,
&isdir);
if (rc == STATUS_NO_MORE_ENTRIES)
{
break;
}
if (isdir == True)
{
AfpInitUnicodeStringWithNonNullTerm(&udirname,
(USHORT)nodenamelen,
nodename);
if (RtlEqualUnicodeString(&Dot,&udirname,False) ||
RtlEqualUnicodeString(&DotDot,&udirname,False))
{
continue;
}
//
// push it onto the dir node stack
//
rc = afpPushDirNode(&DirNodeStacktop,
&pcurrentnode->wdn_RelativePath,
&udirname);
if (rc != STATUS_SUCCESS)
{
status = rc;
break;
}
}
else
{
//
// its a file, call worker with its relative handle
// and path
//
rc = NodeWorker(&pcurrentnode->wdn_Handle,
nodename,
nodenamelen,
False);
if (!NT_SUCCESS(rc))
{
status = rc;
break;
}
}
} // while more entries in the enumbuf
if (!NT_SUCCESS(status))
{
break;
}
} // while there are more files to enumerate
if (pcurrentnode->wdn_Handle.fsh_FileHandle != phTargetDir->fsh_FileHandle)
{
AfpIoClose(&pcurrentnode->wdn_Handle);
}
}
else // we have already enumerated this directory
{
if (pcurrentnode->wdn_RelativePath.Length != 0)
{
// call the worker routine on this directory node
rc = NodeWorker(phTargetDir,
pcurrentnode->wdn_RelativePath.Buffer,
pcurrentnode->wdn_RelativePath.Length,
True);
}
else rc = STATUS_SUCCESS;
afpPopDirNode(&DirNodeStacktop);
if (!NT_SUCCESS(rc))
{
status = rc;
break;
}
}
if (!NT_SUCCESS(status))
{
break;
}
} // while there are directories to pop
} while (False); // error handling loop
while (DirNodeStacktop != NULL)
{
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
("AfpWalkDirectoryTree: WARNING: cleaning up dir stack\n") );
// clean up in case of error
afpPopDirNode(&DirNodeStacktop);
}
AfpFreePANonPagedMemory(enumbuf, AFP_ENUMBUF_SIZE);
return status;
}
/*** afpPushDirNode
*
* Keep a record of all the directories we have encountered so far during
* enumeration of the tree. We need to process directories from the
* bottom up because the WalkTree node worker routine does a delete
* on all the items in a tree, and we certainly cant be deleting directories that
* are not empty.
*
*/
LOCAL
NTSTATUS
afpPushDirNode(
IN OUT PWALKDIR_NODE *ppStacktop,
IN PUNICODE_STRING pParentPath, // path to parent (NULL iff walk root)
IN PUNICODE_STRING pDirName // name of current node directory
)
{
PWALKDIR_NODE tempptr;
UNICODE_STRING ubackslash;
ULONG memsize;
USHORT namesize = 0;
PAGED_CODE( );
if (pParentPath != NULL)
{
namesize = pParentPath->Length + sizeof(WCHAR) +
pDirName->Length + sizeof(UNICODE_NULL);
}
memsize = namesize + sizeof(WALKDIR_NODE);
if ((tempptr = (PWALKDIR_NODE)AfpAllocNonPagedMemory(memsize)) == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
tempptr->wdn_Enumerated = False;
tempptr->wdn_Handle.fsh_FileHandle = NULL;
tempptr->wdn_RelativePath.Length = 0;
tempptr->wdn_RelativePath.MaximumLength = namesize;
if (pParentPath != NULL)
{
tempptr->wdn_RelativePath.Buffer = (LPWSTR)((PBYTE)tempptr +
sizeof(WALKDIR_NODE));
if (pParentPath->Length != 0)
{
RtlInitUnicodeString(&ubackslash,L"\\");
AfpCopyUnicodeString(&tempptr->wdn_RelativePath,pParentPath);
RtlAppendUnicodeStringToString(&tempptr->wdn_RelativePath,
&ubackslash);
}
RtlAppendUnicodeStringToString(&tempptr->wdn_RelativePath,pDirName);
tempptr->wdn_RelativePath.Buffer[tempptr->wdn_RelativePath.Length/sizeof(WCHAR)] = UNICODE_NULL;
}
else
{
tempptr->wdn_RelativePath.Buffer = NULL;
}
// push it on the stack
tempptr->wdn_Next = *ppStacktop;
*ppStacktop = tempptr;
return STATUS_SUCCESS;
}
/*** afpPopDirNode
*
* Pop the top DirNode off of the stack and free it
*
***/
LOCAL
VOID
afpPopDirNode(
IN OUT PWALKDIR_NODE *ppStackTop
)
{
PWALKDIR_NODE tempptr;
PAGED_CODE( );
ASSERT(*ppStackTop != NULL);
tempptr = (*ppStackTop)->wdn_Next;
AfpFreeMemory(*ppStackTop);
*ppStackTop = tempptr;
}
/*** AfpGetNextDirectoryInfo
*
* Given a buffer full of FILE_DIRECTORY_INFORMATION entries as returned
* from a directory enumerate, find the next structure in the buffer and
* return the name information out of it, and whether or not the item
* is a file or directory. Also update the ppInfoBuf to point to the next
* available entry to return for the next time this routine is called.
*
*/
NTSTATUS
AfpGetNextDirectoryInfo(
IN OUT PFILE_DIRECTORY_INFORMATION *ppInfoBuf,
OUT PWCHAR *pNodeName,
OUT PULONG pNodeNameLen,
OUT PBOOLEAN pIsDir
)
{
PFILE_DIRECTORY_INFORMATION tempdirinfo;
PAGED_CODE( );
if (*ppInfoBuf == NULL)
{
return STATUS_NO_MORE_ENTRIES;
}
tempdirinfo = *ppInfoBuf;
if (tempdirinfo->NextEntryOffset == 0)
{
*ppInfoBuf = NULL;
}
else
{
(PBYTE)*ppInfoBuf += tempdirinfo->NextEntryOffset;
}
*pIsDir = (tempdirinfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
True : False;
*pNodeNameLen = tempdirinfo->FileNameLength;
*pNodeName = tempdirinfo->FileName;
return STATUS_SUCCESS;
}
/*** afpNwtDeleteFileEntity
*
* Delete a file or directory opening it with the name relative to phRelative
* handle.
* NOTE: can we use NtDeleteFile here since we dont really care about
* any security checking? Then we wouldn't even have to open a handle,
* although that routine opens one for DELETE_ON_CLOSE for us, then
* closes it.
*/
LOCAL
NTSTATUS
afpNwtDeleteFileEntity(
IN PFILESYSHANDLE phRelative,
IN PWCHAR Name,
IN ULONG Namelen,
IN BOOLEAN IsDir
)
{
ULONG OpenOptions;
FILESYSHANDLE hEntity;
NTSTATUS rc;
UNICODE_STRING uname;
PAGED_CODE( );
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
("\nafpNwtDeleteFileEntity entered\n"));
OpenOptions = IsDir ? FILEIO_OPEN_DIR : FILEIO_OPEN_FILE;
AfpInitUnicodeStringWithNonNullTerm(&uname,(USHORT)Namelen,Name);
rc = AfpIoOpen(phRelative,
AFP_STREAM_DATA,
OpenOptions,
&uname,
FILEIO_ACCESS_DELETE,
FILEIO_DENY_ALL,
False,
&hEntity);
if (!NT_SUCCESS(rc))
{
return rc;
}
rc = AfpIoMarkFileForDelete(&hEntity, NULL, NULL, NULL);
if (!NT_SUCCESS(rc))
{
// If the file is marked readonly, try clearing the RO attribute
if (((rc == STATUS_ACCESS_DENIED) || (rc == STATUS_CANNOT_DELETE)) &&
(NT_SUCCESS(AfpIoSetTimesnAttr(&hEntity,
NULL,
NULL,
0,
FILE_ATTRIBUTE_READONLY,
NULL,
NULL))))
{
rc = AfpIoMarkFileForDelete(&hEntity, NULL, NULL, NULL);
}
if (!NT_SUCCESS(rc))
{
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_ERR,
("\nafpNwtDeleteFileEntity: could not delete file/dir (rc=0x%lx)\n",rc));
DBGBRK(DBG_LEVEL_ERR);
}
}
// NOTE: if marking it for delete fails, at least we could try deleting
// the afpId stream so that we wouldn't find it at some future point...
AfpIoClose(&hEntity);
return rc;
}