experiment : 基于wdk's cancelsafe 例程, 拒绝文件创建
2013-07-27 23:00
417 查看
WDK7600中提供了cancelSafe例程, 用于演示如何将挂起的IRP操作放入挂住队列.
但是例子中在挂住队列WorkItem中的操作只是超时, 超时后, 还是会让文件继续按照原来的请求继续操作.
我想做的是: 在基于MiniFilter的主防中, 遇到有r3请求的文件后缀文件被创建时, 将拦到的IRP操作插入挂住队列,返回挂住状态.
然后由r3决断, 是否对该文件创建操作放行还是拒绝.
网上找了很长时间, 没有找到基于minifilter + cancelsafe, 对文件操作进行干预的Demo。
网上的一般做法都是拦到文件操作时, 直接让r3裁决. 这样可能有效率问题, 没有充分利用minfilter提供的挂住机制 .
今天实验出来了.
发现在挂住队列的WorkItem中, 需要同时设置CALL_BACK_DATA 和 NTSTATUS, 然后调用 FltCompletePendedPreOperation
然后就看见效果(拒绝文件创建或允许文件创建).
我的工程基于WDK提供的miniflter那几个Demo, 根据需要整理了一下, 和原始Demo有点不像了~
下面是有关于 minifilter + cancelSafe 干预文件操作的代码片段,
DataQueue.h, DataQueue.c : cancelsafe操作中的挂住队列操作整理在这2个文件中.
但是例子中在挂住队列WorkItem中的操作只是超时, 超时后, 还是会让文件继续按照原来的请求继续操作.
我想做的是: 在基于MiniFilter的主防中, 遇到有r3请求的文件后缀文件被创建时, 将拦到的IRP操作插入挂住队列,返回挂住状态.
然后由r3决断, 是否对该文件创建操作放行还是拒绝.
网上找了很长时间, 没有找到基于minifilter + cancelsafe, 对文件操作进行干预的Demo。
网上的一般做法都是拦到文件操作时, 直接让r3裁决. 这样可能有效率问题, 没有充分利用minfilter提供的挂住机制 .
今天实验出来了.
发现在挂住队列的WorkItem中, 需要同时设置CALL_BACK_DATA 和 NTSTATUS, 然后调用 FltCompletePendedPreOperation
然后就看见效果(拒绝文件创建或允许文件创建).
我的工程基于WDK提供的miniflter那几个Demo, 根据需要整理了一下, 和原始Demo有点不像了~
下面是有关于 minifilter + cancelSafe 干预文件操作的代码片段,
DataQueue.h, DataQueue.c : cancelsafe操作中的挂住队列操作整理在这2个文件中.
/// @file DataQueue.h /// @brief 数据队列的定义 #ifndef __DATA_QUEUE_H__ #define __DATA_QUEUE_H__ #include <fltKernel.h> #include <dontuse.h> #include <suppress.h> #include "constDefine.h" /// Queue context data structure typedef struct _QUEUE_CONTEXT { FLT_CALLBACK_DATA_QUEUE_IO_CONTEXT CbdqIoContext; /// 队列中的文件上报信息 TAG_FILE_OPT_INFO fileInfo; } QUEUE_CONTEXT, *PQUEUE_CONTEXT; /// Instance context data structure typedef struct _INSTANCE_CONTEXT { /// Instance for this context. PFLT_INSTANCE Instance; /// Cancel safe queue members FLT_CALLBACK_DATA_QUEUE Cbdq; LIST_ENTRY QueueHead; FAST_MUTEX Lock; /// Flag to control the life/death of the work item thread volatile LONG WorkerThreadFlag; /// Notify the worker thread that the instance is being torndown KEVENT TeardownEvent; } INSTANCE_CONTEXT, *PINSTANCE_CONTEXT; /*++ Routine Description: FltMgr calls this routine to acquire the lock protecting the queue. Arguments: DataQueue - Supplies a pointer to the queue itself. Irql - Returns the previous IRQL if a spinlock is acquired. We do not use any spinlocks, so we ignore this. Return Value: None. --*/ VOID CbdqAcquire( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __out PKIRQL Irql); /*++ Routine Description: FltMgr calls this routine to release the lock protecting the queue. Arguments: DataQueue - Supplies a pointer to the queue itself. Irql - Supplies the previous IRQL if a spinlock is acquired. We do not use any spinlocks, so we ignore this. Return Value: None. --*/ VOID CbdqRelease( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in KIRQL Irql ); /*++ Routine Description: FltMgr calls this routine to insert an entry into our pending I/O queue. The queue is already locked before this routine is called. Arguments: DataQueue - Supplies a pointer to the queue itself. Data - Supplies the callback data for the operation that is being inserted into the queue. Context - Supplies user-defined context information. Return Value: STATUS_SUCCESS if the function completes successfully. Otherwise a valid NTSTATUS code is returned. --*/ NTSTATUS CbdqInsertIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in PFLT_CALLBACK_DATA Data, __in_opt PVOID Context ); /*++ Routine Description: FltMgr calls this routine to remove an entry from our pending I/O queue. The queue is already locked before this routine is called. Arguments: DataQueue - Supplies a pointer to the queue itself. Data - Supplies the callback data that is to be removed. Return Value: None. --*/ VOID CbdqRemoveIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in PFLT_CALLBACK_DATA Data ); /*++ Routine Description: FltMgr calls this routine to look for an entry on our pending I/O queue. The queue is already locked before this routine is called. Arguments: DataQueue - Supplies a pointer to the queue itself. Data - Supplies the callback data we should start our search from. If this is NULL, we start at the beginning of the list. PeekContext - Supplies user-defined context information. Return Value: A pointer to the next callback data structure, or NULL. --*/ PFLT_CALLBACK_DATA CbdqPeekNextIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in_opt PFLT_CALLBACK_DATA Data, __in_opt PVOID PeekContext ); /*++ Routine Description: FltMgr calls this routine to complete an operation as cancelled that was previously pended. The queue is already locked before this routine is called. Arguments: DataQueue - Supplies a pointer to the queue itself. Data - Supplies the callback data that is to be canceled. Return Value: None. --*/ VOID CbdqCompleteCanceledIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __inout PFLT_CALLBACK_DATA Data ); /*++ Routine Description: This WorkItem routine is called in the system thread context to process all the pended I/O in this mini filter's cancel safe queue. For each I/O in the queue, it completes the I/O after pending the operation for a period of time. The thread exits when the queue is empty. Arguments: WorkItem - Unused. Filter - Unused. Context - Context information. Return Value: None. --*/ VOID PreIrpMjCreateWorkItemRoutine( __in PFLT_GENERIC_WORKITEM WorkItem, __in PFLT_FILTER Filter, __in PVOID Context ); /*++ Routine Description: This routine waits for a period of time or until the instance is torndown. Arguments: InstanceContext - Supplies a pointer to the instance context. Return Value: The return value is the status of the operation. --*/ NTSTATUS PreIrpMjCreatePendIo( __in PINSTANCE_CONTEXT InstanceContext ); /// @fn PreIrpMjCreateProcessIo /// @brief 如果要拒绝, 必须要同时设置Data和callbackStatus, 如下组合 /// pData->IoStatus.Status = STATUS_ACCESS_DENIED; /// *pcbStatus = FLT_PREOP_COMPLETE; /// @param PFLT_CALLBACK_DATA pData /// Supplies the callback data that was removed from the queue. /// @param PINSTANCE_CONTEXT pInstCtx /// 上下文, 有事件 TeardownEvent 等待r3通知 /// @param FLT_PREOP_CALLBACK_STATUS * pcbStatus /// 回调的状态 /// @return NTSTATUS /// 返回值用不到, 都设置在pData和cbStatus中 NTSTATUS PreIrpMjCreateProcessIo( PFLT_CALLBACK_DATA pData, PINSTANCE_CONTEXT pInstCtx, FLT_PREOP_CALLBACK_STATUS * pcbStatus); /*++ Routine Description: This routine empties the cancel safe queue and complete all the pended pre-read operations. Arguments: InstanceContext - Supplies a pointer to the instance context. Return Value: None. --*/ VOID PreIrpMjCreateEmptyQueueAndComplete( __in PINSTANCE_CONTEXT InstanceContext ); /// @fn ConvertR3Answer /// @brief 将r3回答的动作码, 转换成r0的IO状态码 NTSTATUS ConvertR3Answer(DWORD dwAnswerByR3); #endif // #ifndef __DATA_QUEUE_H__
/// @file DataQueue.c /// @brief Cbdq callback routines #include "DataQueue.h" #include "DebugDefine.h" #include "constDefine.h" #include "timeWrapper.h" #include "LockOpt.h" extern const UNICODE_STRING CbdqFile; VOID CbdqAcquire( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __out PKIRQL Irql ) { PINSTANCE_CONTEXT InstCtx; UNREFERENCED_PARAMETER( Irql ); DebugTrace( DBGLOG_CBDQ_CALLBACK, ("[%ws]: CbdqAcquire\n", DRIVER_NAME) ); /// Get a pointer to the instance context. InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq ); /// Acquire the lock. ExAcquireFastMutex( &InstCtx->Lock ); } VOID CbdqRelease( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in KIRQL Irql ) { PINSTANCE_CONTEXT InstCtx; UNREFERENCED_PARAMETER( Irql ); DebugTrace( DBGLOG_CBDQ_CALLBACK, ("[%ws]: CbdqRelease\n", DRIVER_NAME) ); /// Get a pointer to the instance context. InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq ); /// Release the lock. ExReleaseFastMutex( &InstCtx->Lock ); } NTSTATUS CbdqInsertIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in PFLT_CALLBACK_DATA Data, __in_opt PVOID Context ) { PINSTANCE_CONTEXT InstCtx; PFLT_GENERIC_WORKITEM WorkItem = NULL; NTSTATUS Status = STATUS_SUCCESS; BOOLEAN WasQueueEmpty; UNREFERENCED_PARAMETER( Context ); DebugTrace( DBGLOG_CBDQ_CALLBACK, ("[%ws]: CbdqInsertIo\n", DRIVER_NAME) ); /// Get a pointer to the instance context. InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq ); /// Save the queue state before inserting to it. WasQueueEmpty = IsListEmpty( &InstCtx->QueueHead ); /// Insert the callback data entry into the queue. InsertTailList( &InstCtx->QueueHead, &Data->QueueLinks ); /// Queue a work item if no worker thread present. if (WasQueueEmpty && (1 == InterlockedIncrement( &InstCtx->WorkerThreadFlag ))) { WorkItem = FltAllocateGenericWorkItem(); if (WorkItem) { Status = FltQueueGenericWorkItem( WorkItem, InstCtx->Instance, PreIrpMjCreateWorkItemRoutine, DelayedWorkQueue, InstCtx->Instance ); if (!NT_SUCCESS(Status)) { DebugTrace( DBGLOG_CBDQ_CALLBACK | DBGLOG_ERR, ("[%ws]: Failed to queue the work item (Status = " "0x%x)\n", DRIVER_NAME, Status) ); FltFreeGenericWorkItem( WorkItem ); } } else Status = STATUS_INSUFFICIENT_RESOURCES; if (!NT_SUCCESS(Status)) { /// Remove the callback data that was inserted into the queue. RemoveTailList( &InstCtx->QueueHead ); } } return Status; } VOID CbdqRemoveIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in PFLT_CALLBACK_DATA Data ) { UNREFERENCED_PARAMETER( DataQueue ); DebugTrace( DBGLOG_CBDQ_CALLBACK, ("[%ws]: CbdqRemoveIo\n", DRIVER_NAME)); /// Remove the callback data entry from the queue. RemoveEntryList(&Data->QueueLinks); } PFLT_CALLBACK_DATA CbdqPeekNextIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __in_opt PFLT_CALLBACK_DATA Data, __in_opt PVOID PeekContext ) { PINSTANCE_CONTEXT InstCtx; PLIST_ENTRY NextEntry; PFLT_CALLBACK_DATA NextData; UNREFERENCED_PARAMETER( PeekContext ); DebugTrace( DBGLOG_CBDQ_CALLBACK, ("[%ws]: CbdqPeekNextIo\n", DRIVER_NAME) ); /// Get a pointer to the instance context. InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq ); /// If the supplied callback "Data" is NULL, the "NextIo" is the first /// entry in the queue; or it is the next list entry in the queue. if (Data == NULL) NextEntry = InstCtx->QueueHead.Flink; else NextEntry = Data->QueueLinks.Flink; /// Return NULL if we hit the end of the queue or the queue is empty. if (NextEntry == &InstCtx->QueueHead) return NULL; NextData = CONTAINING_RECORD( NextEntry, FLT_CALLBACK_DATA, QueueLinks ); return NextData; } VOID CbdqCompleteCanceledIo( __in PFLT_CALLBACK_DATA_QUEUE DataQueue, __inout PFLT_CALLBACK_DATA Data ) { PQUEUE_CONTEXT QueueCtx; UNREFERENCED_PARAMETER( DataQueue ); DebugTrace( DBGLOG_CBDQ_CALLBACK, ("[%ws]: CbdqCompleteCanceledIo\n", DRIVER_NAME) ); QueueCtx = (PQUEUE_CONTEXT) Data->QueueContext[0]; /// Just complete the operation as canceled. Data->IoStatus.Status = STATUS_CANCELLED; Data->IoStatus.Information = 0; FltCompletePendedPreOperation( Data, FLT_PREOP_COMPLETE, 0 ); /// Free the extra storage that was allocated for this canceled I/O. ExFreeToNPagedLookasideList( &g_FilterData.OueueContextLookaside, QueueCtx ); } VOID PreIrpMjCreateWorkItemRoutine( __in PFLT_GENERIC_WORKITEM WorkItem, __in PFLT_FILTER Filter, __in PVOID Context ) { PINSTANCE_CONTEXT InstCtx = NULL; PFLT_CALLBACK_DATA Data = NULL; PFLT_INSTANCE Instance = (PFLT_INSTANCE)Context; PQUEUE_CONTEXT QueueCtx; NTSTATUS Status; FLT_PREOP_CALLBACK_STATUS callbackStatus; UNREFERENCED_PARAMETER(WorkItem); UNREFERENCED_PARAMETER(Filter); DebugTrace( DBGLOG_CBDQ_PRE_OPT, ("[%ws]: PreIrpMjCreateWorkItemRoutine\n", DRIVER_NAME) ); /// Get a pointer to the instance context. Status = FltGetInstanceContext( Instance, &InstCtx ); if (!NT_SUCCESS( Status )) { ASSERT( !"Instance Context is missing" ); return; } /// Process all the pended I/O in the cancel safe queue for (;;) { callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; /// 因为没有办法不动挂住的队列, 这函数不干活 PreIrpMjCreatePendIo(InstCtx); /// WorkerThreadFlag >= 1; /// Here we reduce it to 1. InterlockedExchange( &InstCtx->WorkerThreadFlag, 1 ); /// Remove an I/O from the cancel safe queue. Data = FltCbdqRemoveNextIo( &InstCtx->Cbdq, NULL); if (Data) { QueueCtx = (PQUEUE_CONTEXT) Data->QueueContext[0]; /// 通知r3裁决, 或自行判断 PreIrpMjCreateProcessIo(Data, InstCtx, &callbackStatus); /// Check to see if we need to lock the user buffer. /// If the FLTFL_CALLBACK_DATA_SYSTEM_BUFFER flag is set we don't /// have to lock the buffer because its already a system buffer. /// If the MdlAddress is NULL and the buffer is a user buffer, /// then we have to construct one in order to look at the buffer. /// If the length of the buffer is zero there is nothing to read, /// so we cannot construct a MDL. if ( !FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_SYSTEM_BUFFER) && Data->Iopb->Parameters.Read.MdlAddress == NULL && Data->Iopb->Parameters.Read.Length > 0 ) { Status = FltLockUserBuffer(Data); if (!NT_SUCCESS(Status)) { /// If could not lock the user buffer we cannot /// allow the IO to go below us. Because we are /// in a different VA space and the buffer is a /// user mode address, we will either fault or /// corrpt data DebugTrace( DBGLOG_CBDQ_PRE_OPT | DBGLOG_ERR, ("[%ws]: Failed to lock user buffer " "(Status = 0x%x)\n", DRIVER_NAME, Status) ); callbackStatus = FLT_PREOP_COMPLETE; Data->IoStatus.Status = Status; } } /// Complete the I/O FltCompletePendedPreOperation( Data, callbackStatus, NULL ); /// Free the extra storage that was allocated for this I/O. ExFreeToNPagedLookasideList( &g_FilterData.OueueContextLookaside, QueueCtx ); } else { /// At this moment it is possible that a new IO is being inserted /// into the queue in the CbdqInsertIo routine. Now that the queue /// is empty, CbdqInsertIo needs to make a decision on whether to /// create /// a new worker thread. The decision is based on the race between /// the InterlockedIncrement in CbdqInsertIo and the /// InterlockedDecrement as below. There are two situations: /// (1) If the decrement executes earlier before the increment, /// the flag will be decremented to 0 so this worker thread /// will return. Then CbdqInsertIo will increment the flag /// from 0 to 1, and therefore create a new worker thread. /// (2) If the increment executes earlier before the decrement, /// the flag will be first incremented to 2 in CbdqInsertIo /// so a new worker thread will not be satisfied. Then the /// decrement as below will lower the flag down to 1, and /// therefore continue this worker thread. if (InterlockedDecrement( &InstCtx->WorkerThreadFlag ) == 0) break; } } /// Clean up FltReleaseContext(InstCtx); FltFreeGenericWorkItem(WorkItem); } NTSTATUS PreIrpMjCreatePendIo( __in PINSTANCE_CONTEXT InstanceContext ) { UNREFERENCED_PARAMETER(InstanceContext); /// 用户做裁决时, 不能动 InstanceContext->Cbdq /// 没有找到 FltCbdqGet之类的函数, 只有下面这些 /* FltCbdqInitialize FltCbdqEnable FltCbdqDisable FltCbdqInsertIo FltCbdqRemoveIo FltCbdqRemoveNextIo */ /// 所以不能在这让用户裁决, 调整 : 在 PreIrpMjCreateProcessIo 做裁决 return STATUS_SUCCESS; } NTSTATUS PreIrpMjCreateProcessIo( PFLT_CALLBACK_DATA pData, PINSTANCE_CONTEXT pInstCtx, FLT_PREOP_CALLBACK_STATUS * pcbStatus) { LARGE_INTEGER DueTime; NTSTATUS Status = STATUS_SUCCESS; PQUEUE_CONTEXT pQueueCtx = NULL; DebugTrace(DBGLOG_CDO_OPT, (">> PreIrpMjCreateProcessIo\r\n")); /// 为了防止和r3操作 g_FilterData 产生冲突, 需要加锁 LockResourceExclusive(&g_FilterData.ResourceWithR3); /// 判断 应用层是否传来了通讯通知的事件句柄 /// 如果不能和r3通讯, 设置pData状态为(允许且没有后续回调) if (NULL == g_FilterData.g_pEventObject) { pData->IoStatus.Status = STATUS_SUCCESS; goto _PreIrpMjCreateProcessIo_END; } /// 将 pData 数据分享为一个结构, 带锁操作 pQueueCtx = (PQUEUE_CONTEXT)pData->QueueContext[0]; g_FilterData.g_pFileOptInfo = &pQueueCtx->fileInfo; /// 状态值初值(pQueueCtx.fileInfo.dwAnswerByR3)已经为允许, /// 如果R3没应答, 默认是放行的 /// 复位队列等待事件 g_FilterData.pTeardownEvent = &pInstCtx->TeardownEvent; KeResetEvent(&pInstCtx->TeardownEvent); /// 设置R3事件, 通知R3来拿 KeSetEvent(g_FilterData.g_pEventObject, 0, FALSE); UnLockResource(&g_FilterData.ResourceWithR3); /// Delay R3_ANSWER_TIME_OUT_BY_SECOND seconds /// or get signaled if the instance is torndown. DueTime.QuadPart = GetSecondCnt(R3_ANSWER_TIME_OUT_BY_SECOND); DebugTrace(DBGLOG_CDO_OPT, (">> KeWaitForSingleObject\r\n")); Status = KeWaitForSingleObject( &pInstCtx->TeardownEvent, Executive, KernelMode, FALSE, &DueTime); DebugTrace(DBGLOG_CDO_OPT, ("<< KeWaitForSingleObject\r\n")); LockResourceExclusive(&g_FilterData.ResourceWithR3); /// 将状态值设置进pData, 由队列处理工作线程做后续处理 /// 根据用户决断, pData 和 cbStatus 都要设置!!! pData->IoStatus.Status = ConvertR3Answer( pQueueCtx->fileInfo.dwAnswerByR3); *pcbStatus = (STATUS_ACCESS_DENIED == pData->IoStatus.Status) ? FLT_PREOP_COMPLETE : FLT_PREOP_SUCCESS_NO_CALLBACK; /// 数据已经被r3取过了, 清掉 /// r3接到通知后, 如果(NULL != g_FilterData.g_pFileOptInfo), 会来取数据 g_FilterData.g_pFileOptInfo = NULL; g_FilterData.pTeardownEvent = NULL; ///< r3收完数据,不会通知驱动了 _PreIrpMjCreateProcessIo_END: UnLockResource(&g_FilterData.ResourceWithR3); return Status; } NTSTATUS ConvertR3Answer(DWORD dwAnswerByR3) { NTSTATUS status = STATUS_SUCCESS; switch(dwAnswerByR3) { case R3_ANSWER_REFUSE: status = STATUS_ACCESS_DENIED; break; case R3_ANSWER_PASS: default: status = STATUS_SUCCESS; break; } return status; } VOID PreIrpMjCreateEmptyQueueAndComplete( __in PINSTANCE_CONTEXT InstanceContext ) { NTSTATUS status; FLT_PREOP_CALLBACK_STATUS callbackStatus; PFLT_CALLBACK_DATA Data; PQUEUE_CONTEXT QueueCtx; do { callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; Data = FltCbdqRemoveNextIo( &InstanceContext->Cbdq, NULL ); if (Data) { QueueCtx = (PQUEUE_CONTEXT) Data->QueueContext[0]; /// Check to see if we need to lock the user buffer. /// If the FLTFL_CALLBACK_DATA_SYSTEM_BUFFER flag is set we don't /// have to lock the buffer because its already a system buffer. /// If the MdlAddress is NULL and the buffer is a user buffer, /// then we have to construct one in order to look at the buffer. /// If the length of the buffer is zero there is nothing to read, /// so we cannot construct a MDL. if ( !FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_SYSTEM_BUFFER) && Data->Iopb->Parameters.Read.MdlAddress == NULL && Data->Iopb->Parameters.Read.Length > 0 ) { status = FltLockUserBuffer( Data ); if ( ! NT_SUCCESS( status ) ) { /// If could not lock the user buffer we cannot /// allow the IO to go below us. Because we are /// in a different VA space and the buffer is a /// user mode address, we will either fault or /// corrpt data callbackStatus = FLT_PREOP_COMPLETE; Data->IoStatus.Status = status; } } FltCompletePendedPreOperation( Data, callbackStatus, NULL ); ExFreeToNPagedLookasideList( &g_FilterData.OueueContextLookaside, QueueCtx ); } } while (Data); }
相关文章推荐
- NSFileHandle & NSFileManager 文件创建及写入例程
- BC30138: 无法在路径“C:\WINDOWS\TEMP\”中创建临时文件: 拒绝访问
- 基于VS2008使用def文件创建DLL
- 无法在路径“C:\WINDOWS\TEMP\”中创建临时文件: 拒绝访问。
- "某个程序安装已在安装计算机上创建挂起的文件操作" 解决办法
- SqlServer,无法为该请求检索数据, 尝试打开或创建物理文件,CREATE FILE 遇到操作系统错误(拒绝访问)。
- 判断文件是否存在,不存在创建文件&&判断文件夹是否存在,不存在创建文件夹
- Laravel中没有php artisan make:view怎么办?(可以基于Artisan View扩展包创建视图文件的方法解决!)
- 无法打开物理文件mdf,操作系统错误 5:"5(拒绝訪问。)"
- cs0016:未能写入输出文件 "c:"WINDOWS"Microsoft.NET"Framework"v2.0.50727"Temporary ASP.NET Files"root"...."*.dll--“拒绝访问”
- JavaScript创建与读写本地文件(IE&Firefox)
- 创建基于arm的debian文件系统
- 错误日志——Sql附加数据库无法打开物理文件“xxx.mdf"。操作系统错误 5:"5(拒绝访问。)"。 (Microsoft SQL Server,错误: 5120)”
- 解决:CS0016: 未能写入输出文件 "....Framework\v4.0.30319\Temporary ASP.NET Files..."拒绝访问。
- Django 基于Ajax & form 简单实现文件上传
- 错误 4 创建调试信息文件 ……obj\Debug\Sims.Reports.Data.pdb: 拒绝访问
- 解决CD12"未能创建DOM文件,没有注册类别,校验MSXML4安装&
- linux tar:谨慎地拒绝创建空归档文件
- 解决安装sqlServer2000提示 "以前的某个程序安装已经在计算机上创建挂起的文件操作,必须重启计算机"问题