From: Mathieu on 19 Sep 2010 12:54 problem solved with : function NTSTATUS CdFilterCallbackAcquireForCreateSection see osronline Mathieu a �crit : > > Hi > > I am my driver virtual filesystem. > > When I open a file from the virtual drive filesystem is ok for reading > file. > > By cons when I have a blue screen when i call the CreateFileMapping on > my handle file opened on my virtual drive filesystem. > > > NTSTATUS > DrvDispatch ( > IN PDEVICE_OBJECT DeviceObject, > IN PIRP Irp > ) > { > PIO_STACK_LOCATION irps; > NTSTATUS status=STATUS_NOT_IMPLEMENTED; > KdPrintf(("[VFUM] control\n")); > irps = IoGetCurrentIrpStackLocation(Irp); > if (irps!=NULL) > { > KdPrintf(("[VFUM] vfums_control : Device:%x Majorfunction %d > irp %x\n",DeviceObject,irps->MajorFunction,Irp)); > > if (DeviceObject==g_devcontrol) > { > ... > ... > } > else > { > PPARAMVDF vdf=vdf_GetParam(DeviceObject); > KdPrintf(("[VFUM] dispatch_virtualdisk enter : Device:%x > Majorfunction %d irp %x vdf=%x \n",DeviceObject,irps->MajorFunction,vdf)); > > > if (vdf!=NULL) > { > > PVOID* p=NULL; > FsRtlEnterFileSystem(); > if (irps->FileObject!=NULL) > p=irps->FileObject->FsContext2; > KdPrintf(("[VFUM] vfums_control_virtualdisk irp %x > irps %x context %x vdf=%x\n",Irp,irps,p,vdf)); > if ((vdf->used==TRUE) && (vdf->parameter==TRUE)) > { > ----------------------------------------------------FUNCTION FOR > SECTION------------------ > > status=ccreate(DeviceObject,Irp,irps,irps->FileObject); > ------------------------------------------------------------------------------------------ > > if (status!=STATUS_SUCCESS) > { > KdPrintf(("CCReate return status > %x\n",status)); > goto trierrorop; > } > if > ((irps->MajorFunction==IRP_MJ_DIRECTORY_CONTROL)) > { > if > ((irps->MinorFunction==IRP_MN_NOTIFY_CHANGE_DIRECTORY)) > { > PPARAMVDF vdf; > ULONG CompletionFilter; > PFILE_OBJECT file; > BOOLEAN WatchTree; > > file=irps->FileObject; > > KdPrintf(("IRP_MN_NOTIFY_CHANGE_DIRECTORY r�pertoire %ws > bt\n",file->FileName)); > CompletionFilter = > irps->Parameters.NotifyDirectory.CompletionFilter; > WatchTree = (irps->Flags& > SL_WATCH_TREE)==SL_WATCH_TREE ; > > vdf=(PPARAMVDF)DeviceObject->DeviceExtension; > > KdPrintf(("IRP_MN_NOTIFY_CHANGE_DIRECTORY Before Context > %x\n",file->FsContext2)); > FsRtlNotifyFullChangeDirectory( > vdf->NotifySync, > &vdf->DirNotifyList, > file->FsContext2, > (PSTRING)&file->FileName, > WatchTree, > FALSE, > CompletionFilter, > Irp, > NULL, > NULL ); > > KdPrintf(("IRP_MN_NOTIFY_CHANGE_DIRECTORY After Context > %x\n",file->FsContext2)); > Irp->IoStatus.Status=STATUS_SUCCESS; > status=STATUS_PENDING; > FsRtlExitFileSystem(); > goto gty; > > > } > } > if ((irps->MajorFunction==IRP_MJ_READ) || > (irps->MajorFunction==IRP_MJ_WRITE)) > { > ULONG Length; > PMDL mdl; > if (irps->MajorFunction==IRP_MJ_READ) > Length = irps->Parameters.Read.Length; > else > Length = irps->Parameters.Write.Length; > KdPrintf(("Int IRP_MJ_READWRITE Offset %x > Length %x\n",irps->Parameters.Read.ByteOffset.LowPart,Length)); > mdl = IoAllocateMdl(Irp->UserBuffer, > irps->Parameters.QueryDirectory.Length, FALSE, FALSE, Irp); > __try { > > MmProbeAndLockPages(Irp->MdlAddress, Irp->RequestorMode, IoWriteAccess); > } __except > (EXCEPTION_EXECUTE_HANDLER) > { > KdPrintf(("IRP_MJ_READ or > IRP_MJ_WRITE MmProveAndLockPages error\n")); > IoFreeMdl(Irp->MdlAddress); > Irp->MdlAddress = NULL; > status= > STATUS_INSUFFICIENT_RESOURCES; > goto trierrorop; > } > > } > if (irps->MajorFunction==IRP_MJ_DIRECTORY_CONTROL) > { > if > (irps->MinorFunction==IRP_MN_QUERY_DIRECTORY) > { > UCHAR* Buffer; > int cdebug; > UCHAR cdebug1; > ULONG Length = > irps->Parameters.QueryDirectory.Length; > /*est-ce*/ > > PMDL mdl = > IoAllocateMdl(Irp->UserBuffer, irps->Parameters.QueryDirectory.Length, > FALSE, FALSE, Irp); > __try { > > MmProbeAndLockPages(Irp->MdlAddress, Irp->RequestorMode, IoWriteAccess); > } __except > (EXCEPTION_EXECUTE_HANDLER) > { > > KdPrintf(("IRP_MJ_DIRECTORY_CONTROL IRP_MN_QUERY_DIRECTORY > MmProveAndLockPages error\n")); > IoFreeMdl(Irp->MdlAddress); > Irp->MdlAddress = NULL; > status= > STATUS_INSUFFICIENT_RESOURCES; > goto trierrorop; > } > > Buffer=FatMapUserBuffer(Irp); > //Buffer=NULL; > > for (cdebug = 0 ; cdebug < Length; > cdebug++) > { > cdebug1=Buffer[cdebug]; > } > } > } > //KIRQL oldirql; > //MUTEX_P(vdf->lock,&oldirql); > //status=vdfdispatch(DeviceObject,Irp,irps); > //PPARAMVDF > vd=(PPARAMVD)DeviceObject->DeviceExtension; > IjThread(vdf,Irp); > //Irp->IoStatus.Status=STATUS_PENDING; > status=STATUS_PENDING; > > trierrorop: > if (status!=STATUS_PENDING) > { > Irp->IoStatus.Status=status; > IoCompleteRequest(Irp, IO_NO_INCREMENT); > } > //trierror: > FsRtlExitFileSystem(); > return status; > //MUTEX_V(vdf->lock,oldirql); > } > else > { > KdPrintf(("[VFUM] dispatch_virtualdisk : > Device:%x Majorfunction %d irp %x not configured or not initialized %d > %d \n",DeviceObject,irps->MajorFunction,Irp,vdf->used,vdf->parameter)); > status=STATUS_DEVICE_NOT_READY; > > } > } > else > { > KdPrintf(("Handle not found\n")); > status=STATUS_INVALID_HANDLE; > } > } > Irp->IoStatus.Status=status; > } > > IoCompleteRequest(Irp, IO_NO_INCREMENT); > gty: > return status; > > > > > > } > > > VOID VFUM_DeviceThread(IN PVOID Context) > { > PDEVICE_OBJECT device_object; > LARGE_INTEGER time_out; > PLIST_ENTRY request; > NTSTATUS status; > PIO_STACK_LOCATION io_stack; > PIRP irp; > PPARAMVDF vdf; > device_object=(PDEVICE_OBJECT)Context; > vdf=(PPARAMVDF)device_object->DeviceExtension; > vdf->terminate_thread=FALSE; > vdf->threadsec=KeGetCurrentThread(); > > while (device_object->Flags & DO_DEVICE_INITIALIZING) > { > LARGE_INTEGER wait_time; > > KdPrintf(("[VDUM] [Thread] Driver still initializing, waiting 100 > ms...\n")); > > wait_time.QuadPart = -1000000; > KeDelayExecutionThread(KernelMode, FALSE, &wait_time); > } > for (;;) > { > > > HANDLE hprocessID=NULL; > PENTRYL entryl; > KIRQL oldirql; > > request=NULL; > request = > ExInterlockedRemoveHeadList(&vdf->list_head,&vdf->list_lock); > > > > > > > if (request == NULL) > { > if (!vdf->terminate_thread) > { > > // KdPrintferror(("[VDUM] > [Thread]KeWaitForSingleObject %d \n",nc)); > > KeWaitForSingleObject(&vdf->request_event, > Executive, KernelMode, FALSE, NULL); > continue; > } > else > { > KdPrintf(("[VDUM] [Thread]Signal end Thread\n")); > PsTerminateSystemThread(STATUS_SUCCESS); > continue; > } > > > } > //pvd->stackiodisk--; > entryl=CONTAINING_RECORD(request,TENTRYL,le); > irp=entryl->irp; > hprocessID=entryl->processID; > ExFreePool(entryl); > //KdPrintferror(("ProcessID (%x) > (%x)\n",hprocessID,PsGetCurrentProcessId())); > //irp = CONTAINING_RECORD(request, IRP, Tail.Overlay.ListEntry); > KdPrintf(("[VDUM] Thread Irp %x\n",irp)); > { > > NTSTATUS status; > if (!unloaddriver) > { > if ((vdf->used==TRUE) && (vdf->parameter==TRUE)) > { > io_stack = IoGetCurrentIrpStackLocation(irp); > status=vdfdispatch(device_object,irp,io_stack); > } > else > status=STATUS_DEVICE_NOT_READY; > } > else > status=STATUS_DEVICE_REMOVED; > if ((io_stack->MajorFunction==IRP_MJ_READ) || > (io_stack->MajorFunction==IRP_MJ_WRITE)) > { > FreeMdl(irp); > } > if (io_stack->MajorFunction==IRP_MJ_DIRECTORY_CONTROL) > { > if (io_stack->MinorFunction==IRP_MN_QUERY_DIRECTORY) > { > FreeMdl(irp); > } > } > > if (status==STATUS_NOT_IMPLEMENTED) > { > KdPrintf(("NOT IMPLEMENTED\n")); > } > irp->IoStatus.Status=status; > IoCompleteRequest(irp, > > IO_NO_INCREMENT); > KdPrintf(("[VDUM] [Thread]End IoCompleteRequest status > %x\n",status)); > } > } > } > > > void IjThread(PPARAMVDF pvd,IN PIRP irp) > { > PENTRYL entryl; > HANDLE hp; > > KIRQL oldirql; > hp=PsGetCurrentProcessId(); > > KdPrintf(("Begin IjThread (%x)\n",hp)); > irp->IoStatus.Status=STATUS_PENDING; > // MUTEX_P(pvd->lockijt,&oldirql); > IoMarkIrpPending(irp); > > > > > > entryl=(PENTRYL)ExAllocatePoolWithTag(NonPagedPool,sizeof > (TENTRYL),45); > if (entryl!=NULL) > { > entryl->irp=irp; > entryl->processID=hp; > // pvd->stackiodisk++; > > ExInterlockedInsertTailList(&pvd->list_head, > &entryl->le, > &pvd->list_lock); > > > > > } > else > KdPrintf(("Error allocation entryl\n",NULL)); > //*status= STATUS_PENDING; > //irp->IoStatus.Status=STATUS_PENDING; > KeSetEvent(&pvd->request_event, (KPRIORITY) 0, FALSE); > > } > > > In my thread i'am function under : > NTSTATUS vdfdispatch(PDEVICE_OBJECT DeviceObject,PIRP > irp,PIO_STACK_LOCATION irps) > { > .. > .. > NTSTATUS status; > status=STATUS_NOT_IMPLEMENTED; > > switch (irps->MajorFunction) > { > case IRP_MJ_CREATE: > KdPrintf(("IRP_MJ_CREATE irp:%x irps:%x\n",irp,irps)); > status = FatCommonCreate( DeviceObject,irp,irps ); > break; > case IRP_MJ_READ: > KdPrintf(("IRP_MJ_READ irp:%x irps:%x\n",irp,irps)); > status = FatCommonRead( DeviceObject,irp,irps ); > break; > .. > .. > case IRP_MJ_CLOSE: > KdPrintf(("IRP_MJ_CLOSE irp:%x irps:%x\n",irp,irps)); > status = FatCommonClose( DeviceObject,irp,irps); > break; > case IRP_MJ_CLEANUP: > { > PPARAMVDF vdf=DeviceObject->DeviceExtension; > KdPrintf(("IRP_MJ_CLEANUP irp:%x irps:%x\n",irp,irps)); > if (irps->FileObject!=NULL) > { > KdPrintf((" IRP_MJ_CLEANUP ->FsContext2 before > = %x\n",irps->FileObject->FsContext2)); > //suspicion code! > if (irps->FileObject!=NULL) > FsRtlNotifyCleanup( vdf->NotifySync, > &vdf->DirNotifyList, > irps->FileObject->FsContext2 ); > KdPrintf((" IRP_MJ_CLEANUP ->FsContext2 after = > %x\n",irps->FileObject->FsContext2)); > /// > } > status=FatCleanup(DeviceObject,irp,irps); > > //status=STATUS_SUCCESS; > } > break; > > ... > ... > return status; > > } > > Before call function FatCommonCreate FatCleanup , i call the function > ccreate. > > PVOID > NTAPI > ExAllocatePoolWithTagZero( > __in __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType, > __in SIZE_T NumberOfBytes, > __in ULONG Tag > ) > { > PVOID r=ExAllocatePoolWithTag(PoolType,NumberOfBytes,Tag); > if (r!=NULL) > { > RtlZeroMemory(r,NumberOfBytes); > return r; > } > else > KdPrintf(("Failure allocation tag advancedFCB\n")); > return NULL; > } > > > typedef struct > { > ERESOURCE MainResource; > ERESOURCE PagingIoResource; > ERESOURCE Resource; > FAST_MUTEX HeaderMutex; > SECTION_OBJECT_POINTERS section_object_pointers; > FSRTL_ADVANCED_FCB_HEADER FCBHeader; > __int64 Context; > }TFSC,*PFSC; > > > ------------------------------------FONCTION SECTION MAPPING > FILE--------------------------- > NTSTATUS ccreate(PDEVICE_OBJECT DeviceObject,PIRP irp,PIO_STACK_LOCATION > irps,PFILE_OBJECT file) > { > NTSTATUS r=STATUS_SUCCESS; > switch (irps->MajorFunction) > { > case IRP_MJ_CREATE: > { > PFSC fsc=NULL; > KdPrintf(("IRP_MJ_CREATE irp:%x > irps:%x\n",irp,irps)); > > fsc=(PFSC)ExAllocatePoolWithTagZero(NonPagedPool,sizeof (TFSC),0xfeea); > > > > if (fsc!=NULL) > { > > > ExInitializeResourceLite(&fsc->MainResource); > ExInitializeResourceLite(&fsc->PagingIoResource); > ExInitializeFastMutex(&fsc->HeaderMutex); > //ExInitializeResourceLite(&fsc->Resource); > > > FsRtlSetupAdvancedHeader(&fsc->FCBHeader, > &fsc->HeaderMutex); > > if (irps->Flags & SL_OPEN_PAGING_FILE) { > fsc->FCBHeader.Flags2 |= > FSRTL_FLAG2_IS_PAGING_FILE; > fsc->FCBHeader.Flags2 &= > ~FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS; > } > > > // ExAcquireResourceExclusiveLite(&fcs->Resource, > TRUE); > > > fsc->FCBHeader.ValidDataLength.LowPart = > 0xffffffff; > fsc->FCBHeader.ValidDataLength.HighPart = > 0x7fffffff; > > fsc->FCBHeader.Resource = &fsc->MainResource; > fsc->FCBHeader.PagingIoResource = > &fsc->PagingIoResource; > > fsc->FCBHeader.AllocationSize.QuadPart = 4096; > fsc->FCBHeader.FileSize.QuadPart = 4096; > > fsc->FCBHeader.IsFastIoPossible = > FastIoIsNotPossible; > > > > //fsc->Context=(PVOID)cc->context; > //if (cc->section==TRUE) > > file->SectionObjectPointer=&fsc->section_object_pointers; > file->FsContext=&fsc->FCBHeader; > file->FsContext2=fsc; > file->PrivateCacheMap=NULL; > //ExReleaseResourceLite(&fcs->Resource); > > } > else > { > KdPrintf(("FatCreate > fsc=NULL\n")); > r=STATUS_INSUFFICIENT_RESOURCES; > } > } > break; > case IRP_MJ_CLEANUP: > { > PFSC fsc=(PFSC)file->FsContext2; > if (fsc!=NULL) > { > if (file->SectionObjectPointer != NULL && > file->SectionObjectPointer->DataSectionObject != > NULL) { > KdPrintf(("FatCleanup Dump Cache\n")); > CcFlushCache(&fsc->section_object_pointers, > NULL, 0, NULL); > > CcPurgeCacheSection(&fsc->section_object_pointers, NULL, 0, FALSE); > CcUninitializeCacheMap(file, NULL, NULL); > > > } > file->Flags |= FO_CLEANUP_COMPLETE; > } > > } > break; > case IRP_MJ_CLOSE: > { > PFSC fsc=NULL; > fsc=file->FsContext2; > __try > { > FsRtlTeardownPerStreamContexts(&fsc->FCBHeader); > ExDeleteResourceLite(&fsc->PagingIoResource); > ExDeleteResourceLite(&fsc->MainResource); > } > __except (1) > { > KdPrintf(("FatCleanup Exception > dExDeleteResourceLite\n")); > } > > > } > break; > } > return r; > } > > ;----------------------------------------------------------------- > > i release pointer "fsc" in a function FatCommonClose. > > I'am not forget to call function IoCompleteRequest. > > In My function FatCommonClose : i have this line : > file->FsContext=NULL; > file->FsContext2=NULL; > > > NTSTATUS FatCommonClose( PDEVICE_OBJECT DeviceObject,PIRP > irp,PIO_STACK_LOCATION irps) > { > __try > { > PPARAMVDF vdf; > NTSTATUS status; > NTSTATUS error; > PFILE_OBJECT file; > PFILE_OBJECT relatedfile; > UCHAR* c; > int size; > KdPrintf(("FatCommonClose\n")); > file=irps->FileObject; > relatedfile=file->RelatedFileObject; > if (file==NULL) > { > KdPrintf(("file=NULL Why ?\n")); > return STATUS_INTERNAL_ERROR; > } > relatedfile=file->RelatedFileObject; > if (relatedfile==NULL) > { > KdPrintf(("relatedfile=NULL Why ?\n")); > relatedfile=file; > } > vdf=vdf_GetParam(DeviceObject); > > KdPrintf(("FatCommonClose FileName %ws RelatedFileName %ws > vdf:%x\n",(VOID*)file->FileName.Buffer,(VOID*)relatedfile->FileName.Buffer,vdf)); > > if (vdf==NULL) > { > KdPrintf(("FatCommonClose : vdf==NULL")); > return STATUS_INVALID_HANDLE; > > } > size=file->FileName.Length+2+relatedfile->FileName.Length+2; > //size=size<<1; > c=(WCHAR*)ExAllocatePoolWithTag(NonPagedPool,size+128,43); > if (c!=NULL) > { > THANDLES h; > KdPrintf2(("memset c,0\n")); > memset(c,0,size); > > //KdPrintf2(("memcpy(c+0,file->FileName.Buffer,file->FileName.Length*2);\n")); > > if > (file->FileName.Buffer!=NULL)memcpy(c+0,file->FileName.Buffer,file->FileName.Length); > > > //KdPrintf2(("memcpy(c+(1+(file->FileName.Length*2)),relatedfile->FileName.Buffer);\n")); > > if > (relatedfile->FileName.Buffer!=NULL)memcpy(c+(2+(file->FileName.Length*1)),relatedfile->FileName.Buffer,relatedfile->FileName.Length); > > > FillHandle(&h,irp,irps); > KdPrintf2(("Calluser\n")); > status=CallUser(vdf,CMD_CLOSEFILE,sizeof > (THANDLES),&h,size,c,&error); > KdPrintf2(("ExFreePool\n")); > ExFreePool(c); > KdPrintf2(("Fin\n")); > > { > PFSC fsc=NULL; > fsc=file->FsContext2; > __try > { > ExFreePool(fsc); > } > __except (1) > { > KdPrintf(("FatCleanup Exception ExFreeePool fsc\n")); > } > > file->FsContext=NULL; > file->FsContext2=NULL; > } > > if (error==STATUS_TIMEOUT) > return STATUS_DEVICE_NOT_READY; > else > return error; > } > else > return STATUS_INSUFFICIENT_RESOURCES; > > } > __except (1) > { > KdPrintf(("FatCommmonClose exception\n")); > return STATUS_INTERNAL_ERROR; > } > return STATUS_SUCCESS; > > } > > Also i'am define a function Auto Zero Memory: > PVOID > NTAPI > ExAllocatePoolWithTagZero( > __in __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType, > __in SIZE_T NumberOfBytes, > __in ULONG Tag > ) > { > PVOID r=ExAllocatePoolWithTag(PoolType,NumberOfBytes,Tag); > if (r!=NULL) > { > RtlZeroMemory(r,NumberOfBytes); > return r; > } > else > KdPrintf(("Failure allocation tag advancedFCB\n")); > return NULL; > } > > I imitate Dokan. > > Thank you for help me. > > ----------------------------------------------------------------- > > Blue Screen: A thread tried to release a resource it Did Not Own. > > STACK_TEXT: > WARNING: Stack unwind information not available. Following frames may be > wrong. > b21f6abc 8052cab4 000000e3 816ea008 816e9b30 nt!KeBugCheckEx 0x1b > b21f6af0 805ee6cf 81d598f8 00000000 81d598f8 nt!KePulseEvent 0x10519 > b21f6c38 804f629f 81cdaf90 00000000 000000c8 > nt!FsRtlDeleteKeyFromTunnelCache 0x2b99 > b21f6cd0 8056eef7 b21f6d1c 000f0005 00000000 nt!FsRtlFastUnlockAll 0x4e6 > b21f6d40 804ddf0f 0012fdac 000f0005 00000000 nt!NtCreateSection 0xd2 > b21f6e2c 7c923212 7c923281 00003288 00000000 nt!KiDeliverApc 0xbbb > b21f6e30 7c923281 00003288 00000000 7ff47c18 ntdll!LdrLockLoaderLock 0xa1 > b21f6e60 7c923288 000066f1 00000000 01d0052d ntdll!LdrUnlockLoaderLock 0x58 > b21f6e70 7c92657e 00000000 00000000 00000002 ntdll!LdrUnlockLoaderLock 0x5f > b21f6ed4 7c92657e 7c92659e 00000001 00000001 ntdll!LdrLoadDll 0x3b4 > b21f6ed8 7c92659e 00000001 00000001 00000000 ntdll!LdrLoadDll 0x3b4 > b21f6ef4 7c80e693 00000001 00000000 0f72f9e4 ntdll!LdrGetDllHandle 0x18 > b21f6f04 7c80e6a3 00000000 7ff47c00 00000002 kernel32!GetModuleHandleW 0x57 > b21f6f18 7c9201c7 00000008 00000001 00000000 kernel32!GetModuleHandleW 0x67 > b21f6f30 7c9202d2 00000000 00000000 00140000 > ntdll!RtlAddRefActivationContext 0xe5 > b21f6f4c 7c9202ed 00000001 ffffffff 00000000 > ntdll!RtlQueryInformationActivationContext 0xf0 > b21f6f88 7c91e3ed 7c9232f8 00000018 0f72fd80 > ntdll!RtlQueryInformationActivationContext 0x10b > b21f6f8c 7c9232f8 00000018 0f72fd80 0f72fd80 > ntdll!NtRequestWaitReplyPort 0xc > b21f6fac 7c81079e 7c91e46b 7c8107e1 00000908 ntdll!CsrClientCallServer 0x57 > b21f6fb0 7c91e46b 7c8107e1 00000908 7c8107fd kernel32!CreateRemoteThread > 0x178 > b21f6fb4 7c8107e1 00000908 7c8107fd 5be458cc ntdll!NtResumeThread 0xc > b21f6fbc 7c8107fd 5be458cc 0ed429e8 00000000 kernel32!CreateRemoteThread > 0x1bb > b21f6fdc 7c80e6a3 7c80e7ab 00000103 7ff46000 kernel32!CreateRemoteThread > 0x1d7 > b21f6fe0 7c80e7ab 00000103 7ff46000 00000000 kernel32!GetModuleHandleW 0x67 > b21f6fe4 00000000 7ff46000 00000000 00000000 kernel32!GetModuleHandleW > 0x16f
|
Pages: 1 Prev: WDDK driver problem Next: Enable/Disable NIC most portable way. |