/* 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 #include #include #include #include #include #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; }