Duplicate file handle to avoid Sharing Violation Error Message
I´m trying to work on a incremental backup system and when I run my script, I always get the "Cannot open file due to sharing violation" as the file I´m trying to open to do the incremental backup is already in use by another process. I know I can kill this process, free up the file and do the incremental backup, but that´s something I really have to avoid. I´ve read that with the Win32 API I could duplicate the file handler, so how should I do.
Here is a piece of my code:
FILE *GetFileHandle(WIN32_FIND_DATA *pWfdStruct, BOOL bWrite){
FILE *fFile;
DWORD nGLE;
fFile = fopen(pWfdStruct->cFileName, "rb");
if (!fFile)
{
nGLE = GetLastError();
if (nGLE == ERROR_SHARING_VIOLATION) // 32
{
char szCurDir[8192];
GetCurrentDirectory(8192, szCurDir);
ODS("WARN: cannot open %s file due to sharing violation [fRenameFile: %s\\%s]\n",
bWrite ? "dst" : "src", szCurDir, pWfdStruct->cFileName);
return 0;
}
if (nGLE == ERROR_ACCESS_DENIED) // 5
{
char szCurDir[8192];
GetCurrentDirectory(8192, szCurDir);
ODS("WARN: cannot open %s file, access denied [fRenameFile: %s\\%s]\n",
bWrite ? "dst" : "src", szCurDir, pWfdStruct->cFileName);
return 0;
}
if (nGLE == ERROR_FILE_NOT_FOUND) // 2
{
char szCurDir[8192];
GetCurrentDirectory(8192, szCurDir);
ODS("WARN: cannot open %s file, file not present [fRenameFile: %s\\%s]\n",
bWrite ? "dst" : "src", szCurDir, pWfdStruct->cFileName);
return 0;
}
char szCurDir[8192];
GetCurrentDirectory(8192, szCurDir);
if (bWrite)
{
ODS("WARN: cannot open dst file [fRenameFile: %s\\%s] [GLE: %d]\n",
szCurDir, pWfdStruct->cFileName, nGLE);
return 0;
}
ODS("WARN: cannot op开发者_开发知识库en src file [fRenameFile: %s\\%s] [GLE: %d] trying alt name [%s]\n",
szCurDir, pWfdStruct->cFileName, nGLE, pWfdStruct->cAlternateFileName);
ReportSystemError("GetFileHandle", nGLE);
__try
{
if (pWfdStruct->cAlternateFileName[0])
{
fFile = fopen(pWfdStruct->cAlternateFileName, "rb");
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ODS("Exception caught\n"); // give up
}
if (!fFile)
{
nGLE = GetLastError();
ReportSystemError("GetFileHandle 2nd try", nGLE);
FATALODS("FATAL error, cannot open src file [%s] [GLE: %d]", pWfdStruct->cFileName, nGLE);
}
else
{
ODS("File: %s open success\n", pWfdStruct->cAlternateFileName);
}
}
return fFile;} // GetFileHandle
Could you please help me?
If the file's locked by another process, you can't open it. Period.
Windows has a Volume Shadow Copy service (VSS) specifically for the purpose of building backup clients. It has provisions for copying locked files, ensuring consistent snapshots etc. The API is a bit convoluted but there's no other way if you want a robust backup solution.
You can look at an open-source HoboCopy backup tool for an example of using VSS.
EDIT: After some googling I've found two examples that do what you want. I can't guarante these work, but at least they look plausible.
The first example, clear and well-commented (the language is some dialect of Basic, but very readable):
$include "windowssdk.inc"
$include "ntddk.inc"
$include "undocumented.inc"
$include "stdio.inc"
$use "_crtdll.lib"
'-------------------------- example
$include "shlwapi.inc"
$define REPLACE_ALWAYS
' open youtube in internet explorer, watch any video (should be downloaded in 100%)
' then run this program to copy the locked fla***.tmp file from your TEMP directory
istring tmppath[MAX_PATH]
' 1. enumerate fla*.tmp files in TEMP directory
ExpandEnvironmentStrings("%TEMP%\\", tmppath, MAX_PATH)
int pathlen = len(tmppath)
strcpy(&tmppath[pathlen], "fla*.tmp")
UINT hFind = findopen(tmppath)
int nFilesFound = 0
int nFilesCopied = 0
if (hFind)
istring name[256] = findnext(hFind)
while (name[0] <> 0)
strncpy(&tmppath[pathlen], name, MAX_PATH-pathlen)
' tmppath = local path to flash video
' 2. copy the file with renamed extension (supported by media player classic)
istring newpath[MAX_PATH]
newpath = tmppath
PathRenameExtension(newpath, ".flv")
' check if we can open the file directly
HANDLE hFile = CreateFile(tmppath, GENERIC_READ, FILE_SHARE_READ _
| FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, 0, 0)
if (hFile <> INVALID_HANDLE_VALUE)
' file opened, so you can copy it with your favorite file manager
CloseHandle(hFile)
else
nFilesFound++
' the file is opened with exclusive access, call the subroutine below to open it
HANDLE hProcess
hFile = OpenFileEx(tmppath, &hProcess)
if (hFile = INVALID_HANDLE_VALUE)
' failed
MessageBox 0, "failed to open " + tmppath, ""
else
' copy it now ...
$ifdef REPLACE_ALWAYS
HANDLE hOutFile = CreateFile(newpath, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0)
$else
HANDLE hOutFile = CreateFile(newpath, GENERIC_WRITE, 0, 0, CREATE_NEW, 0, 0)
$endif
if (hOutFile = INVALID_HANDLE_VALUE)
' failed ?
MessageBox 0, "failed to create " + newpath, ""
else
' ... but first suspend the owner process (because the handle is duplicated, and
' if the owner process changes file pointer, it will be reflected also in this process)
declare import, NtSuspendProcess(HANDLE hProcess), NTSTATUS
declare import, NtResumeProcess(HANDLE hProcess), NTSTATUS
NtSuspendProcess(hProcess)
' save original file pointer in originalFilePos variable
LARGE_INTEGER originalFilePos
LARGE_INTEGER nullFilePos
nullFilePos.QuadPart = 0q
SetFilePointerEx(hFile, nullFilePos, &originalFilePos, FILE_CURRENT)
' seek to beginning
SetFilePointerEx(hFile, nullFilePos, NULL, FILE_BEGIN)
' copy, using string name as the buffer
DWORD BytesRead, BytesWriten
while (ReadFile(hFile, &name, 256, &BytesRead, 0) and BytesRead)
WriteFile(hOutFile, name, BytesRead, &BytesWriten, 0)
endwhile
'
nFilesCopied++
' restore file pointer
SetFilePointerEx(hFile, originalFilePos, NULL, FILE_BEGIN)
' cleanup
CloseHandle(hOutFile)
' resume the process
NtResumeProcess(hProcess)
endif
CloseHandle(hFile)
endif
endif
name = findnext(hFind)
endwhile
findclose(hFind)
endif
if (MessageBox(0, using("Copied # from # locked .FLV files. Open Directory ?", nFilesCopied, nFilesFound), "", MB_YESNO) = IDYES)
tmppath[pathlen-1] = 0
system tmppath
endif
'-------------------------- code
type SYSTEM_HANDLE_ENTRY
ULONG OwnerPid
BYTE ObjectType
BYTE HandleFlags
USHORT HandleValue
PVOID ObjectPointer
ACCESS_MASK GrantedAccess
endtype
type SYSTEM_HANDLE_INFORMATION
ULONG HandleCount
SYSTEM_HANDLE_ENTRY Handles[1]
endtype
type MY_OBJECT_TYPE_INFORMATION
OBJECT_TYPE_INFORMATION t
iwstring buffer[64]
endtype
type MY_OBJECT_NAME_INFORMATION
OBJECT_NAME_INFORMATION t
iwstring buffer[280]
endtype
declare import, NtQuerySystemInformation(_
int SystemInformationClass,_
PVOID SystemInformation,_
ULONG SystemInformationLength,_
pointer ReturnLength),NTSTATUS
const SystemHandleInformation = 16
sub OpenFileEx(string path, pointer pphProcess),HANDLE
MY_OBJECT_TYPE_INFORMATION htype
MY_OBJECT_NAME_INFORMATION name
*<HANDLE>pphProcess = 0
HANDLE h = INVALID_HANDLE_VALUE
' convert c:\ to \Device\HardDiskVolume...
' 1. extract partition letter
iwstring root[4]
root[0] = path[0], 58, 0
' 2. convert it to \Device\HardDiskVolumeX
iwstring wszNTPath[280]
int cch = QueryDosDeviceW(root, wszNTPath, 280)
if (!cch) then return INVALID_HANDLE_VALUE
' 3. append remaining folders and file name from string path parameter
' so <path> "c:\Program Files" gets converted to <wszNTPath> "\Device\HardDiskVolume1\Program Files"
cch = wcslen(wszNTPath)
_snwprintf(&wszNTPath[cch], 280-cch, L"%S", &path[2])
' now get the list of all handles, and find the handle which name is equal to wszNTPath
ULONG BytesNeeded, BufferSize = 4096
pointer handles = new(char, BufferSize) ' SYSTEM_HANDLE_INFORMATION*
while (handles)
' get the list of all user-mode handles
NTSTATUS status = NtQuerySystemInformation(SystemHandleInformation, handles, BufferSize, &BytesNeeded)
if (status = STATUS_INFO_LENGTH_MISMATCH)
' BytesNeeded is not adjusted, so we need to increase buffer size
delete handles
BufferSize += 32768
handles = new(char, BufferSize)
elseif (!status)
settype handles, SYSTEM_HANDLE_INFORMATION
' sort handles by owning process id
qsort(&*handles.Handles, *handles.HandleCount, len(SYSTEM_HANDLE_ENTRY), &SortHandlesCb)
pointer node = *handles.Handles
settype node, SYSTEM_HANDLE_ENTRY
ULONG OwnerPid = 0
HANDLE hProcess = 0
HANDLE hThisProcess = GetCurrentProcess()
BYTE ObjectTypeFile = 0
while (*handles.HandleCount)
*handles.HandleCount--
if (*node.GrantedAccess & FILE_READ_DATA) ' 13019F
if (OwnerPid <> *node.OwnerPid)
OwnerPid = *node.OwnerPid
if (hProcess) then CloseHandle(hProcess)
hProcess = OpenProcess(PROCESS_DUP_HANDLE|PROCESS_SUSPEND_RESUME, FALSE, OwnerPid)
endif
if (hProcess)
HANDLE hObject
if (DuplicateHandle(hProcess, *node.HandleValue, hThisProcess, &hObject, 0, FALSE, DUPLICATE_SAME_ACCESS))
if (GetFileType(hObject) = FILE_TYPE_DISK)
if (!ObjectTypeFile) ' query object type name as "integer"
if (!NtQueryObject(hObject, ObjectTypeInformation, &htype, len(htype), &BytesNeeded))
if (!_wcsnicmp(htype.t.TypeName.Buffer, L"File", 4))
ObjectTypeFile = *node.ObjectType
endif
endif
endif
' do not query object name with granted access 0x0012019f (deadloock)
if (ObjectTypeFile and (ObjectTypeFile = *node.ObjectType) and (*node.GrantedAccess <> 0x0012019f))
' query file name
if (!NtQueryObject(hObject, ObjectNameInformation, &name, len(name), &BytesNeeded))
if (name.t.Name.Buffer and !wcsicmp(name.t.Name.Buffer, wszNTPath)) ' compare
*<HANDLE>pphProcess = hProcess
delete handles
return hObject
endif
endif
endif
endif
CloseHandle(hObject)
endif
endif
endif
node = &*node[1]
endwhile
if (hProcess) then CloseHandle(hProcess)
delete handles
else
' NtQuerySystemInformation failed
delete handles
endif
endwhile
if (handles) then delete handles
return h
endsub
declare cdecl SortHandlesCb(SYSTEM_HANDLE_ENTRY p1, SYSTEM_HANDLE_ENTRY p2),int
sub SortHandlesCb(SYSTEM_HANDLE_ENTRY p1, SYSTEM_HANDLE_ENTRY p2),int
if (p1.OwnerPid = p2.OwnerPid) then return 0
if (p1.OwnerPid > p2.OwnerPid) then return 1
return -1
endsub
Another messy example is at http://forum.sysinternals.com/topic7974.html.
Note that both examples use Native API functions and some of their undocumented functionality.
The basic steps are:
- Obtain file handle value as seen by the locking process. This is done by calling
NtQuerySystemInformation
to get the list of all handles in the system andNtQueryObject
to find the one with matching file name. Note that file name must be converted to NT device format. - Open the process owning the handle (
OpenProcess
) with suffucuent privileges (PROCESS_DUP_HANDLE
andPROCESS_SUSPEND_RESUME
) and callDuiplicateHandle
(with that process as the source, handle value from step 1, and your process as the destination). - Use
NtSuspendProcess
to "pause" the process and prevent it from modifying file pointer and file content). - Copy the file using duplicated handle.
- Restore file pointer, close the handle, call
NtResumeProcess
to unpause the process.
EDIT2: Personally I have used another way to access locked files - raw disk access with manual NTFS parsing. E.g. given the file name find its MFT entry, decode data run locations and read them from the raw disk. Raw disk access is always available (provided you have admin privileges), therefore any file is readable. The downside is zero consistency guarantee so it isn't suitable for backup purposes.
PS. If I were you I'd still go with the officially supported VSS. Backup software should not rely on hacks.
精彩评论