* syscalls.cc (enum bin_status): New type.

(try_to_bin): Return bin_status.  Rename win32_path to pc.  Rename h
	to fh.  Rename fh to tmp_fh.  Add code to set delete dispostion and
	more code to replace file moved to bin by another, temporary file.
	Add comments to explain why.
	(unlink_nt): Replace move_to_bin with bin_stat.  Only set bin_stat
	to move_to_bin for non-remote files.  As a last resort, call try_to_bin
	if setting delete-on-close failed.  Only re-set R/O DOS attribute
	and only close handle if it's still valid.
This commit is contained in:
Corinna Vinschen 2009-01-12 15:51:23 +00:00
parent b98c66ee2c
commit 8a0f3bbf2d
2 changed files with 123 additions and 41 deletions

View File

@ -1,3 +1,15 @@
2009-01-12 Corinna Vinschen <corinna@vinschen.de>
* syscalls.cc (enum bin_status): New type.
(try_to_bin): Return bin_status. Rename win32_path to pc. Rename h
to fh. Rename fh to tmp_fh. Add code to set delete dispostion and
more code to replace file moved to bin by another, temporary file.
Add comments to explain why.
(unlink_nt): Replace move_to_bin with bin_stat. Only set bin_stat
to move_to_bin for non-remote files. As a last resort, call try_to_bin
if setting delete-on-close failed. Only re-set R/O DOS attribute
and only close handle if it's still valid.
2009-01-11 Corinna Vinschen <corinna@vinschen.de>
* errno.cc (errmap): Set errno to ENOENT instead of ENOSHARE throughout.

View File

@ -132,13 +132,21 @@ static BYTE info2[] =
0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static void
try_to_bin (path_conv &win32_path, HANDLE h)
enum bin_status
{
dont_move,
move_to_bin,
has_been_moved
};
static bin_status
try_to_bin (path_conv &pc, HANDLE &fh, ACCESS_MASK access)
{
bin_status bin_stat = move_to_bin;
NTSTATUS status;
OBJECT_ATTRIBUTES attr;
IO_STATUS_BLOCK io;
HANDLE rootdir = NULL, recyclerdir = NULL;
HANDLE rootdir = NULL, recyclerdir = NULL, tmp_fh = NULL;
USHORT recycler_base_len = 0, recycler_user_len = 0;
UNICODE_STRING root, recycler, fname;
WCHAR recyclerbuf[NAME_MAX + 1]; /* Enough for recycler + SID + filename */
@ -146,9 +154,10 @@ try_to_bin (path_conv &win32_path, HANDLE h)
PFILE_INTERNAL_INFORMATION pfii;
PFILE_RENAME_INFORMATION pfri;
BYTE infobuf[sizeof (FILE_NAME_INFORMATION ) + 32767 * sizeof (WCHAR)];
FILE_DISPOSITION_INFORMATION disp = { TRUE };
pfni = (PFILE_NAME_INFORMATION) infobuf;
status = NtQueryInformationFile (h, &io, pfni, sizeof infobuf,
status = NtQueryInformationFile (fh, &io, pfni, sizeof infobuf,
FileNameInformation);
if (!NT_SUCCESS (status))
{
@ -168,9 +177,9 @@ try_to_bin (path_conv &win32_path, HANDLE h)
RtlInitEmptyUnicodeString (&recycler, recyclerbuf, sizeof recyclerbuf);
if (wincap.has_recycle_dot_bin ()) /* NTFS and FAT since Vista */
RtlAppendUnicodeToString (&recycler, L"\\$Recycle.Bin\\");
else if (win32_path.fs_is_ntfs ()) /* NTFS up to 2K3 */
else if (pc.fs_is_ntfs ()) /* NTFS up to 2K3 */
RtlAppendUnicodeToString (&recycler, L"\\RECYCLER\\");
else if (win32_path.fs_is_fat ()) /* FAT up to 2K3 */
else if (pc.fs_is_fat ()) /* FAT up to 2K3 */
RtlAppendUnicodeToString (&recycler, L"\\Recycled\\");
else
goto out;
@ -185,7 +194,7 @@ try_to_bin (path_conv &win32_path, HANDLE h)
/* Create root dir path from file name information. */
RtlSplitUnicodePath (&fname, &fname, NULL);
RtlSplitUnicodePath (win32_path.get_nt_native_path (), &root, NULL);
RtlSplitUnicodePath (pc.get_nt_native_path (), &root, NULL);
root.Length -= fname.Length - sizeof (WCHAR);
/* Open root directory. All recycler bin ops are caseinsensitive. */
@ -206,7 +215,7 @@ try_to_bin (path_conv &win32_path, HANDLE h)
/* On NTFS the recycler dir contains user specific subdirs, which are the
actual recycle bins per user. The name if this dir is the string
representation of the user SID. */
if (win32_path.fs_is_ntfs ())
if (pc.fs_is_ntfs ())
{
UNICODE_STRING sid;
WCHAR sidbuf[128];
@ -222,7 +231,7 @@ try_to_bin (path_conv &win32_path, HANDLE h)
/* Create hopefully unique filename. */
RtlAppendUnicodeToString (&recycler, L"\\cyg");
pfii = (PFILE_INTERNAL_INFORMATION) infobuf;
status = NtQueryInformationFile (h, &io, pfii, sizeof infobuf,
status = NtQueryInformationFile (fh, &io, pfii, sizeof infobuf,
FileInternalInformation);
if (!NT_SUCCESS (status))
{
@ -237,7 +246,7 @@ try_to_bin (path_conv &win32_path, HANDLE h)
pfri->RootDirectory = rootdir;
pfri->FileNameLength = recycler.Length;
memcpy (pfri->FileName, recycler.Buffer, recycler.Length);
status = NtSetInformationFile (h, &io, pfri, sizeof infobuf,
status = NtSetInformationFile (fh, &io, pfri, sizeof infobuf,
FileRenameInformation);
if (status == STATUS_OBJECT_PATH_NOT_FOUND)
{
@ -260,7 +269,7 @@ try_to_bin (path_conv &win32_path, HANDLE h)
recycler.Length = recycler_base_len;
status = NtCreateFile (&recyclerdir,
READ_CONTROL
| (win32_path.fs_is_ntfs () ? 0 : FILE_ADD_FILE),
| (pc.fs_is_ntfs () ? 0 : FILE_ADD_FILE),
&attr, &io, NULL,
FILE_ATTRIBUTE_DIRECTORY
| FILE_ATTRIBUTE_SYSTEM
@ -274,7 +283,7 @@ try_to_bin (path_conv &win32_path, HANDLE h)
}
/* Next, if necessary, check if the recycler/SID dir exists and
create it if not. */
if (win32_path.fs_is_ntfs ())
if (pc.fs_is_ntfs ())
{
NtClose (recyclerdir);
recycler.Length = recycler_user_len;
@ -296,11 +305,10 @@ try_to_bin (path_conv &win32_path, HANDLE h)
corrupted */
if (io.Information == FILE_CREATED)
{
HANDLE fh;
RtlInitUnicodeString (&fname, L"desktop.ini");
InitializeObjectAttributes (&attr, &fname, OBJ_CASE_INSENSITIVE,
recyclerdir, NULL);
status = NtCreateFile (&fh, FILE_GENERIC_WRITE, &attr, &io, NULL,
status = NtCreateFile (&tmp_fh, FILE_GENERIC_WRITE, &attr, &io, NULL,
FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
FILE_SHARE_VALID_FLAGS, FILE_CREATE,
FILE_SYNCHRONOUS_IO_NONALERT
@ -309,18 +317,18 @@ try_to_bin (path_conv &win32_path, HANDLE h)
debug_printf ("NtCreateFile (%S) failed, %08x", &recycler, status);
else
{
status = NtWriteFile (fh, NULL, NULL, NULL, &io, desktop_ini,
status = NtWriteFile (tmp_fh, NULL, NULL, NULL, &io, desktop_ini,
sizeof desktop_ini - 1, NULL, NULL);
if (!NT_SUCCESS (status))
debug_printf ("NtWriteFile (%S) failed, %08x", &fname, status);
NtClose (fh);
NtClose (tmp_fh);
}
if (!wincap.has_recycle_dot_bin ()) /* No INFO2 file since Vista */
{
RtlInitUnicodeString (&fname, L"INFO2");
status = NtCreateFile (&fh, FILE_GENERIC_WRITE, &attr, &io, NULL,
FILE_ATTRIBUTE_ARCHIVE
| FILE_ATTRIBUTE_HIDDEN,
status = NtCreateFile (&tmp_fh, FILE_GENERIC_WRITE, &attr, &io,
NULL, FILE_ATTRIBUTE_ARCHIVE
| FILE_ATTRIBUTE_HIDDEN,
FILE_SHARE_VALID_FLAGS, FILE_CREATE,
FILE_SYNCHRONOUS_IO_NONALERT
| FILE_NON_DIRECTORY_FILE, NULL, 0);
@ -329,26 +337,75 @@ try_to_bin (path_conv &win32_path, HANDLE h)
&recycler, status);
else
{
status = NtWriteFile (fh, NULL, NULL, NULL, &io, info2,
status = NtWriteFile (tmp_fh, NULL, NULL, NULL, &io, info2,
sizeof info2, NULL, NULL);
if (!NT_SUCCESS (status))
debug_printf ("NtWriteFile (%S) failed, %08x",
&fname, status);
NtClose (fh);
NtClose (tmp_fh);
}
}
}
NtClose (recyclerdir);
/* Shoot again. */
status = NtSetInformationFile (h, &io, pfri, sizeof infobuf,
status = NtSetInformationFile (fh, &io, pfri, sizeof infobuf,
FileRenameInformation);
}
if (!NT_SUCCESS (status))
debug_printf ("Move %S to %S failed, status = %p",
win32_path.get_nt_native_path (), &recycler, status);
{
debug_printf ("Move %S to %S failed, status = %p",
pc.get_nt_native_path (), &recycler, status);
goto out;
}
/* Moving to the bin worked. */
bin_stat = has_been_moved;
/* Now we try to set the delete disposition. If that worked, we're done.
We try this here first, as long as we still have the open handle.
Otherwise the below code closes the handle to allow replacing the file. */
status = NtSetInformationFile (fh, &io, &disp, sizeof disp,
FileDispositionInformation);
/* In case of success, restore R/O attribute to accommodate hardlinks.
That leaves potentially hardlinks around with the R/O bit suddenly
off if setting the delete disposition failed, but please, keep in
mind this is really a border case only. */
if ((access & FILE_WRITE_ATTRIBUTES) && NT_SUCCESS (status) && !pc.isdir ())
NtSetAttributesFile (fh, pc.file_attributes ());
NtClose (fh);
fh = NULL; /* So unlink_nt doesn't close the handle twice. */
/* On success or when trying to unlink a directory we just return here.
The below code only works for files. */
if (NT_SUCCESS (status) || pc.isdir ())
goto out;
/* The final trick. We create a temporary file with delete-on-close
semantic and rename that file to the file just moved to the bin.
This typically overwrites the original file and we get rid of it,
even if neither setting the delete dispostion, nor setting
delete-on-close on the original file succeeds. There are still
cases in which this fails, for instance, when trying to delete a
hardlink to a DLL used by the unlinking application itself. */
RtlAppendUnicodeToString (&recycler, L"X");
InitializeObjectAttributes (&attr, &recycler, 0, rootdir, NULL);
status = NtCreateFile (&tmp_fh, DELETE, &attr, &io, NULL,
FILE_ATTRIBUTE_NORMAL, 0, FILE_SUPERSEDE,
FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE,
NULL, 0);
if (!NT_SUCCESS (status))
{
debug_printf ("Creating file for overwriting failed, status = %p",
status);
goto out;
}
status = NtSetInformationFile (tmp_fh, &io, pfri, sizeof infobuf,
FileRenameInformation);
NtClose (tmp_fh);
if (!NT_SUCCESS (status))
debug_printf ("Overwriting with another file failed, status = %p",
status);
out:
if (rootdir)
NtClose (rootdir);
return bin_stat;
}
static NTSTATUS
@ -411,11 +468,13 @@ unlink_nt (path_conv &pc)
bin so that it actually disappears from its directory even though its
in use. Otherwise, if opening doesn't fail, the file is not in use and
we can go straight to setting the delete disposition flag. */
bool move_to_bin = false;
bin_status bin_stat = dont_move;
status = NtOpenFile (&fh, access, &attr, &io, FILE_SHARE_DELETE, flags);
if (status == STATUS_SHARING_VIOLATION)
{
move_to_bin = true;
/* Bin is only accessible locally. */
if (!pc.isremote ())
bin_stat = move_to_bin;
if (!pc.isdir () || pc.isremote ())
status = NtOpenFile (&fh, access, &attr, &io,
FILE_SHARE_VALID_FLAGS, flags);
@ -457,14 +516,15 @@ unlink_nt (path_conv &pc)
syscall_printf ("Opening file for delete failed, status = %p", status);
return status;
}
if (move_to_bin && !pc.isremote ())
try_to_bin (pc, fh);
/* Get rid of read-only attribute. */
if (access & FILE_WRITE_ATTRIBUTES)
NtSetAttributesFile (fh, pc.file_attributes () & ~FILE_ATTRIBUTE_READONLY);
/* Try to move to bin if a sharing violation occured. If that worked,
we're done. */
if (bin_stat == move_to_bin
&& (bin_stat = try_to_bin (pc, fh, access)) == has_been_moved)
return 0;
/* Try to set delete disposition. */
FILE_DISPOSITION_INFORMATION disp = { TRUE };
status = NtSetInformationFile (fh, &io, &disp, sizeof disp,
FileDispositionInformation);
@ -475,7 +535,7 @@ unlink_nt (path_conv &pc)
/* Trying to delete a hardlink to a file in use by the system in some
way (for instance, font files) by setting the delete disposition fails
with STATUS_CANNOT_DELETE. Strange enough, deleting these hardlinks
using delete-on-close semantic works.
using delete-on-close semantic works... most of the time.
Don't use delete-on-close on remote shares. If two processes
have open handles on a file and one of them calls unlink, the
@ -494,26 +554,36 @@ unlink_nt (path_conv &pc)
RtlInitUnicodeString (&fname, L"");
InitializeObjectAttributes (&attr, &fname, 0, fh, NULL);
status = NtOpenFile (&fh2, DELETE, &attr, &io,
move_to_bin ? FILE_SHARE_VALID_FLAGS
: FILE_SHARE_DELETE,
bin_stat == move_to_bin ? FILE_SHARE_VALID_FLAGS
: FILE_SHARE_DELETE,
flags | FILE_DELETE_ON_CLOSE);
if (!NT_SUCCESS (status))
syscall_printf ("Setting delete-on-close failed, status = %p",
status);
{
syscall_printf ("Setting delete-on-close failed, status = %p",
status);
/* This is really the last chance. If it hasn't been moved
to the bin already, try it now. If moving to the bin
succeeds, we got rid of the file in some way, even if
unlinking didn't work. */
if (bin_stat == dont_move)
bin_stat = try_to_bin (pc, fh, access);
if (bin_stat == has_been_moved)
status = STATUS_SUCCESS;
}
else
NtClose (fh2);
}
}
if ((access & FILE_WRITE_ATTRIBUTES)
&& (!NT_SUCCESS (status) || !pc.isdir ()))
if (fh)
{
/* Restore R/O attribute to accommodate hardlinks. Don't try this
with directories! For some reason the below NtSetInformationFile
changes the delete disposition back to FALSE, at least on XP. */
NtSetAttributesFile (fh, pc.file_attributes ());
if ((access & FILE_WRITE_ATTRIBUTES)
&& (!NT_SUCCESS (status) || !pc.isdir ()))
NtSetAttributesFile (fh, pc.file_attributes ());
NtClose (fh);
}
NtClose (fh);
return status;
}